问题描述
有个接口使用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,先埋个坑,之后有空再看看⑧