文章目录
- 问题描述
- 问题分析
- 解决
- Thread.sleep
- get()
- Mockito.lenient()
问题描述
有个接口使用CompletableFuture实现的异步调用,现在要用Mockito写单元测试
@Testpublic void updateNumAsync() {Integer newNum = 600;// updateRoleCountAsync用CompletableFuture异步调用的ApiUtil.put发送http请求更新对方服务端的数据// 生成要用的stubwhen(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结束的太早,那么强行让他多等一会是不是就好了?
@Testpublic void updateNumAsync() throws InterruptedException {Integer newNum = 600;// updateRoleCountAsync用CompletableFuture异步调用的ApiUtil.put发送http请求更新对方服务端的数据// 生成要用的stubwhen(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 调用之前被回收。
@Testpublic void updateNumAsync() throws InterruptedException, ExecutionException {Integer newNum = 600;// updateRoleCountAsync用CompletableFuture异步调用的ApiUtil.put发送http请求更新对方服务端的数据// 生成要用的stubwhen(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()
。
这个方法在前面那个问题里也是能让测试通过的。
@Testpublic void updateNumAsync(){Integer newNum = 600;// updateRoleCountAsync用CompletableFuture异步调用的ApiUtil.put发送http请求更新对方服务端的数据// 生成要用的stubMockito.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,先埋个坑,之后有空再看看⑧