You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JUnit异步测试:Mockito verify与doReturn混用及Stubbing异常解决

解决CompletableFuture异步测试中Mockito Stub和验证的问题

核心问题分析

你的问题源于两个关键错误:

  1. Stub与调用顺序完全颠倒:你先执行verify验证,再配置when Stub,此时异步代码还未触发Mock方法调用,必然会报"零交互"错误。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 04:05:26