JUnit异步测试:Mockito verify与doReturn混用及Stubbing异常解决
解决CompletableFuture异步测试中Mockito Stub和验证的问题
核心问题分析
你的问题源于两个关键错误:
- Stub与调用顺序完全颠倒:你先执行
verify验证,再配置whenStub,此时异步代码还未触发Mock方法调用,必然会报"零交互"错误。 - Strict Stubbing参数匹配失败:第一个错误里实际调用
queryForList时第一个参数传了null,但你用的anyString()只匹配非null字符串,不兼容null,导致Stub未命中,触发Mockito Strict模式报错。
最优解决方案步骤
1. 提前配置Stub,覆盖所有可能的调用参数
针对queryForList第一个参数可能为null的情况,调整匹配器确保Stub能命中实际调用:
// 用doReturn避免when模式的潜在副作用(比如Stub时执行真实方法逻辑) Mockito.doReturn(null).when(namedParameterJdbcTemplate) .queryForList(Mockito.any(), Mockito.any(MapSqlParameterSource.class), Mockito.eq(String.class)); Mockito.doReturn(offerEvents).when(namedParameterJdbcTemplate) .query(Mockito.anyString(), Mockito.any(MapSqlParameterSource.class), Mockito.any(BasicMapper.class));
如果需要更精确匹配,也可以用Mockito.or(Mockito.isNull(), Mockito.anyString())指定参数范围。
2. 执行被测异步代码
触发你的业务方法启动异步任务:
// 示例:调用被测的异步方法 CompletableFuture<?> future = yourService.asyncBusinessMethod();
3. 等待异步任务完成后再验证
优先直接等待CompletableFuture完成(比Mockito timeout更可靠),再执行验证:
// 等待异步任务结束 future.join(); // 执行普通验证(无需timeout) Mockito.verify(namedParameterJdbcTemplate, Mockito.times(1)) .queryForList(Mockito.any(), Mockito.any(MapSqlParameterSource.class), Mockito.eq(String.class)); Mockito.verify(namedParameterJdbcTemplate, Mockito.times(1)) .query(Mockito.anyString(), Mockito.any(MapSqlParameterSource.class), Mockito.any(BasicMapper.class));
如果无法直接持有CompletableFuture引用,再退而求其次用Mockito.timeout(500)给异步调用足够的执行时间。
4. Strict Stubbing的补充说明
不建议直接用lenient()绕过检查(会掩盖真正的Stub错误),仅在以下合法场景考虑使用:
- 同一方法需要多次Stub不同参数的情况
- 代码逻辑中确实会用不同参数调用同一Mock方法
完整示例代码
@Test void testAsyncDatabaseCall() { // 1. 提前配置Mock Stub Mockito.doReturn(null).when(namedParameterJdbcTemplate) .queryForList(Mockito.any(), Mockito.any(MapSqlParameterSource.class), Mockito.eq(String.class)); Mockito.doReturn(offerEvents).when(namedParameterJdbcTemplate) .query(Mockito.anyString(), Mockito.any(MapSqlParameterSource.class), Mockito.any(BasicMapper.class)); // 2. 启动异步任务 CompletableFuture<Void> asyncTask = yourService.processOfferEventsAsync(); // 3. 等待异步任务执行完成 asyncTask.join(); // 4. 验证Mock方法的调用情况 Mockito.verify(namedParameterJdbcTemplate, Mockito.times(1)) .queryForList(Mockito.any(), Mockito.any(MapSqlParameterSource.class), Mockito.eq(String.class)); Mockito.verify(namedParameterJdbcTemplate, Mockito.times(1)) .query(Mockito.anyString(), Mockito.any(MapSqlParameterSource.class), Mockito.any(BasicMapper.class)); }
内容的提问来源于stack exchange,提问作者Gojo
相关产品推荐
相关产品推荐

