当前位置: 首页 > article >正文

2.4 异步回调参数捕获技巧详解

异步回调参数捕获技巧详解(修正版)

在测试涉及异步回调的代码时,直接Mock私有方法不可行(Mockito无法Mock私有方法)。正确的做法是通过控制公有方法触发回调,或使用Spy对象间接管理异步逻辑。以下为修正后的完整方案:


1. 重构示例代码(避免Mock私有方法)

假设有一个异步处理器AsyncProcessor,其核心逻辑如下(调整后):

public class AsyncProcessor {
    // 通过公有方法触发异步任务(而非直接暴露私有方法)
    public void processAsync(CompletionCallback callback) {
        new Thread(() -> {
            try {
                String result = doWorkInternal(); // 内部私有方法
                callback.onComplete(result);
            } catch (Exception e) {
                callback.onError(e);
            }
        }).start();
    }

    // 私有方法:实际执行任务
    private String doWorkInternal() throws Exception {
        // 真实业务逻辑(如网络请求、计算等)
        return "SUCCESS";
    }
}

interface CompletionCallback {
    void onComplete(String result);
    void onError(Throwable error);
}

2. 测试策略分析
  • 目标:验证异步任务完成后,回调方法onComplete被调用且参数正确。
  • 挑战
    • 不能直接Mock私有方法doWorkInternal()
    • 需要确保异步任务在测试结束前完成。
  • 解决方案
    1. 使用Spy对象部分模拟:保留异步流程,覆盖关键逻辑。
    2. 显式触发回调:控制测试执行流程。
    3. 参数捕获与验证:结合ArgumentCaptor和异步等待机制。

3. 完整测试代码
@ExtendWith(MockitoExtension.class)
class AsyncProcessorTest {

    @Spy // 使用Spy包装真实对象
    private AsyncProcessor spyAsyncProcessor;

    @Mock
    private CompletionCallback mockCallback;

    @Captor
    private ArgumentCaptor<String> resultCaptor;

    @Captor
    private ArgumentCaptor<Throwable> errorCaptor;

    @Test
    void processAsync_ShouldCallOnCompleteWithResult() throws Exception {
        // 覆盖私有方法的逻辑(通过公有方法间接控制)
        doReturn("MOCK_SUCCESS").when(spyAsyncProcessor).doWorkInternal();

        // 发起异步任务
        spyAsyncProcessor.processAsync(mockCallback);

        // 显式等待异步任务完成(替代Thread.sleep)
        await().atMost(1, TimeUnit.SECONDS)
               .untilAsserted(() -> verify(mockCallback).onComplete(any()));

        // 捕获回调参数并验证
        verify(mockCallback).onComplete(resultCaptor.capture());
        assertEquals("MOCK_SUCCESS", resultCaptor.getValue());
    }

    @Test
    void processAsync_ShouldCallOnErrorIfTaskFails() throws Exception {
        // 模拟任务抛出异常
        doThrow(new RuntimeException("Simulated Failure")).when(spyAsyncProcessor).doWorkInternal();

        // 发起异步任务
        spyAsyncProcessor.processAsync(mockCallback);

        // 等待错误回调
        await().atMost(1, TimeUnit.SECONDS)
               .untilAsserted(() -> verify(mockCallback).onError(any()));

        // 验证错误信息
        verify(mockCallback).onError(errorCaptor.capture());
        assertEquals("Simulated Failure", errorCaptor.getValue().getMessage());
    }
}

4. 关键步骤解析
4.1 使用Spy对象覆盖私有方法
  • 问题:直接调用spyAsyncProcessor.doWorkInternal()会执行真实方法。
  • 解决:通过doReturn().when()覆盖私有方法的行为:
    doReturn("MOCK_SUCCESS").when(spyAsyncProcessor).doWorkInternal();
    
    • 原理:Mockito通过反射机制绕过私有方法限制(需注意方法可见性)。
4.2 异步等待机制
  • 替代Thread.sleep:使用Awaitility库(需添加依赖)更可靠地等待异步结果:
    <dependency>
        <groupId>org.awaitility</groupId>
        <artifactId>awaitility</artifactId>
        <version>4.2.0</version>
        <scope>test</scope>
    </dependency>
    
    • 优势:避免固定等待时间,动态轮询直到条件满足。
4.3 参数捕获与验证
  • 捕获参数:使用ArgumentCaptor获取回调方法的实际参数:
    verify(mockCallback).onComplete(resultCaptor.capture());
    String actualResult = resultCaptor.getValue();
    
  • 精确验证:结合断言库(如AssertJ)增强可读性:
    assertThat(actualResult).isEqualTo("MOCK_SUCCESS");
    

5. 替代方案:直接触发回调

若无法覆盖私有方法,可通过模拟异步流程直接触发回调:

@Test
void processAsync_ShouldTriggerCallbackManually() {
    // 发起异步任务
    spyAsyncProcessor.processAsync(mockCallback);

    // 绕过异步线程,直接触发回调(适用于简单测试)
    mockCallback.onComplete("MANUAL_RESULT");

    // 验证结果
    verify(someService).handleResult("MANUAL_RESULT");
}

6. 最佳实践总结
  • 避免Mock私有方法:优先通过公有方法或Spy对象控制测试逻辑。
  • 使用可靠等待机制Awaitility > Thread.sleep
  • 精准捕获参数:结合ArgumentCaptor和断言库验证细节。
  • 保持测试独立性:每个测试方法重置Mock/Spy状态。

通过以上方案,即使面对私有方法和异步回调的复杂场景,也能高效完成测试验证。


http://www.kler.cn/a/540606.html

相关文章:

  • Vue(4)
  • Qt通过FFmpeg打开RTSP并截图一帧作为背景
  • jemalloc的malloc案例来分析GOT表和PLT表有关流程
  • MySQL下载过程
  • JavaScript入门知识
  • 解决bad SQL grammar []; nested exception is java.sql.SQLSyntaxErrorException
  • 使用LangChain自定义tools的三种方式
  • 穷举vs暴搜vs深搜vs回溯vs剪枝系列一>不同路径 III
  • c/c++蓝桥杯经典编程题100道(19)质因数分解
  • 博客项目-day02(登录功能)
  • Django在终端创建项目(pycharm Windows)
  • Ollama+Chatbox本地部署运行deepseek
  • MySQL主从同步+binlog
  • ffmpeg -demuxers
  • 《optee系统架构从入门到精通》
  • 征程 6 相比征程 5 对算子支持扩展的具体案例讲解
  • 将本地jar包安装到maven仓库
  • 【PCIe 总线及设备入门学习专栏 10 -- pci linux driver】
  • 宝塔一键部署Wordpress无法打开,显示响应时间太长
  • MyBatis——动态SQL
  • 在Linux上创建虚拟网卡
  • Centos7系统安装redis
  • 机器学习分类整理【表格版】分类角度、名称、概念、常见算法、典型案例
  • 《手札·开源篇》Odoo系统与SKF Observer Phoenix API双向对接方案
  • 28、Spring Boot 定时任务:轻松实现任务自动化
  • DatePicker 实现:日期范围截止时间为23:59:59