【Java】CompletableFuture+Mockito单元测试不通过 Unnecessary stubbings detected

问题描述

有个接口使用CompletableFuture实现的异步调用,现在要用Mockito写单元测试

	@Test
    public void updateNumAsync() {
        Integer newNum = 600;
        // updateRoleCountAsync用CompletableFuture异步调用的ApiUtil.put发送http请求更新对方服务端的数据
        // 生成要用的stub
        when(ApiUtil.put(Constants.UPDATE_COUNT, newNum.toString(), serverId)).thenReturn("{\"code\":0}");
        App.updateNumAsync(serverId, newNum).whenComplete((result, throwable) -> {
            assertEquals(result.getCode(), 0);
        });
    }

结果测试不通过:

Tests Failed: 1 of 1 test
Unnecessary stubbings detected.
Clean & maintainable test code requires zero unnecessary code.

问题分析

看控制台输出的意思大概就是when(...).return(...) mock的stub没被用到,然后测试不通过。
因为测试过程主要就是:1)mock一个要用的stub; 2)调用待测接口;3)检查结果。由于这里是异步调用,updateNumAsync里调用的CompletableFuture.supplyAsync()用的ForkJoinPool,会有一个线程1在后台异步执行updateNum的操作,因此猜测可能是当前test的线程0在异步过程中先结束了,导致线程0 Mock的stub并没有被线程1执行的待测试接口用到,导致Tests Failed

解决

Thread.sleep

既然Test的线程0结束的太早,那么强行让他多等一会是不是就好了?

	@Test
    public void updateNumAsync() throws InterruptedException {
        Integer newNum = 600;
        // updateRoleCountAsync用CompletableFuture异步调用的ApiUtil.put发送http请求更新对方服务端的数据
        // 生成要用的stub
        when(ApiUtil.put(Constants.UPDATE_COUNT, newNum.toString(), serverId)).thenReturn("{\"code\":0}");
        App.updateNumAsync(serverId, newNum).whenComplete((result, throwable) -> {
            assertEquals(result.getCode(), 0);
        });
        Thread.sleep(1000L);
    }

结果测试通过,证明之前的猜想应该是对的。但不太推荐这样做。

Tests Passed: 1 of 1 test

get()

CompletableFuture通过get()获取异步调用结果时,会阻塞当前线程直到异步操作结束返回。也就是说test的线程0不会提早结束,导致虚拟机栈中的stub在被线程1 调用之前被回收。

	@Test
    public void updateNumAsync() throws  InterruptedException, ExecutionException {
        Integer newNum = 600;
        // updateRoleCountAsync用CompletableFuture异步调用的ApiUtil.put发送http请求更新对方服务端的数据
        // 生成要用的stub
        when(ApiUtil.put(Constants.UPDATE_COUNT, newNum.toString(), serverId)).thenReturn("{\"code\":0}");
        App.updateNumAsync(serverId, newNum).whenComplete((result, throwable) -> {
            assertEquals(result.getCode(), 0);
        }).get();
    }

结果测试通过.

Tests Passed: 1 of 1 test

Mockito.lenient()

stackoverflow上面有人在mock多个stub的同时(用了get()),但也还会出现Unnecessary stubbings detected.,详情可以看原帖。大概就是有时Mockito可能没有按照确定的顺序调用这些方法,此时就可以用lenient()
这个方法在前面那个问题里也是能让测试通过的。

	@Test
    public void updateNumAsync(){
        Integer newNum = 600;
        // updateRoleCountAsync用CompletableFuture异步调用的ApiUtil.put发送http请求更新对方服务端的数据
        // 生成要用的stub
        Mockito.lenient().when(ApiUtil.put(Constants.UPDATE_COUNT, newNum.toString(), serverId)).thenReturn("{\"code\":0}");
        App.updateNumAsync(serverId, newNum).whenComplete((result, throwable) -> {
            assertEquals(result.getCode(), 0);
        });
    }

具体原理还不是很明白,反正就是能work,先埋个坑,之后有空再看看⑧

Spring Boot是一个用于构建Java应用程序的开源框架,它提供了一种简化了配置的方式来快速构建应用程序。JUnit是一个用于编写和运行单元测试的开源测试框架,而Mockito是一个用于创建和管理模拟对象的Java库。 下面是一个使用Spring Boot、JUnit和Mockito进行单元测试的示例: 假设我们有一个UserService类,它依赖于一个UserRepository接口来访问数据库并进行一些操作。我们想要对UserService的方法进行单元测试。 首先,我们需要创建一个测试类,命名为UserServiceTest。在测试类中,我们将使用JUnit的注解来标记测试方法,并使用Mockito来创建模拟对象。示例代码如下: ```java @RunWith(MockitoJUnitRunner.class) public class UserServiceTest { @InjectMocks private UserService userService; @Mock private UserRepository userRepository; @Test public void testGetUserById() { // 配置模拟对象的行为 User user = new User("1", "John"); when(userRepository.findById("1")).thenReturn(user); // 调用被测试的方法 User result = userService.getUserById("1"); // 验证结果 assertEquals("John", result.getName()); } } ``` 在上面的示例中,我们使用了@RunWith注解来指定使用MockitoJUnitRunner运行测试,这样就能自动创建和管理模拟对象。使用@InjectMocks注解将被测试的对象自动注入到测试类中,使用@Mock注解创建模拟对象。 在testGetUserById方法中,我们首先使用when方法配置userRepository模拟对象的行为,表示当传入参数为"1"时,返回一个指定的User对象。 然后,我们通过调用userService的getUserById方法来测试该方法的逻辑。最后,使用assertEquals断言来验证结果是否符合预期。 以上就是一个使用Spring Boot、JUnit和Mockito进行单元测试的示例。通过使用Mockito创建模拟对象,我们可以更容易地测试各个方法的逻辑,而不依赖于实际的数据库。这样可以提高测试效率并确保代码的质量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值