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

如何用Mockito测试包含CompletableFuture异步调用的Java方法

如何对包含CompletableFuture.supplyAsync的Java方法进行单元测试?

针对你的ServiceA#methodA方法,我来一步步讲解如何编写可靠的单元测试——核心是解决异步任务的测试稳定性问题,同时验证所有依赖组件的交互逻辑。我们会用到JUnit 5和Mockito这两个常用的测试框架。


第一步:准备测试依赖与测试类结构

首先确保项目中引入了JUnit 5和Mockito的依赖(Maven/Gradle均可)。然后创建测试类,通过@Mock注解Mock所有依赖组件,用@InjectMocks注入待测试的ServiceA实例:

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.ResponseEntity;
import java.util.concurrent.CompletableFuture;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class ServiceATest {

    // Mock所有依赖组件
    @Mock
    private ThreadPoolTaskExecutor taskExecutor;
    @Mock
    private ClassA objA;
    @Mock
    private ClassB objB;

    // 注入待测试的ServiceA实例
    @InjectMocks
    private ServiceA serviceA;

    // 测试用例写在这里...
}

第二步:解决异步任务的测试稳定性问题

methodA中使用了自定义线程池taskExecutor执行异步任务,如果直接测试,异步任务可能还没执行完,测试代码就已经走完了,导致验证失败。我们可以通过Mockito让线程池同步执行任务,把异步逻辑变成同步的:

// 在测试用例中添加这段代码,让线程池直接在当前线程执行任务
doAnswer(invocation -> {
    Runnable runnable = invocation.getArgument(0);
    runnable.run(); // 同步执行异步任务
    return null;
}).when(taskExecutor).execute(any(Runnable.class));

这样一来,所有异步逻辑都会在测试线程中同步完成,我们就能准确验证后续的方法调用。


第三步:编写正常流程的测试用例

测试当异步任务正常执行时,所有依赖方法是否被正确调用,以及返回值是否符合预期:

@Test
void methodA_WhenAllStepsSucceed_ReturnsOkAndExecutesAsyncFlow() {
    // 1. 配置线程池同步执行
    doAnswer(invocation -> {
        Runnable runnable = invocation.getArgument(0);
        runnable.run();
        return null;
    }).when(taskExecutor).execute(any(Runnable.class));

    // 2. Mock objA.getData的返回值(替换成你实际的返回类型)
    Object mockDataResult = new Object();
    when(objA.getData(eq(123), any(Object.class))).thenReturn(mockDataResult);

    // 3. 执行待测试方法
    CompletableFuture<ResponseEntity> resultFuture = serviceA.methodA(new Object(), 123);
    ResponseEntity result = resultFuture.join();

    // 4. 验证返回值(你的methodA固定返回OK,所以直接断言)
    assert result.equals(ResponseEntity.ok().build());

    // 5. 验证依赖方法的调用
    verify(objA).getData(eq(123), any(Object.class)); // 检查objA的getData是否用正确参数调用
    verify(objB).doSomeThing(any(), any()); // 检查objB的doSomeThing是否被调用(可根据实际参数调整匹配器)
}

第四步:编写异常流程的测试用例

测试当异步任务抛出异常时,exceptionally分支是否正确处理,以及依赖方法的调用情况:

@Test
void methodA_WhenAsyncTaskThrowsException_HandlesErrorAndReturnsOk() {
    // 1. 同样配置线程池同步执行
    doAnswer(invocation -> {
        Runnable runnable = invocation.getArgument(0);
        runnable.run();
        return null;
    }).when(taskExecutor).execute(any(Runnable.class));

    // 2. Mock objA.getData抛出异常
    when(objA.getData(eq(456), any(Object.class))).thenThrow(new RuntimeException("Test error"));

    // 3. 执行待测试方法
    CompletableFuture<ResponseEntity> resultFuture = serviceA.methodA(new Object(), 456);
    ResponseEntity result = resultFuture.join();

    // 4. 验证返回值仍然是OK
    assert result.equals(ResponseEntity.ok().build());

    // 5. 验证objB的doSomeThing没有被调用(因为异常被捕获了)
    verify(objB, never()).doSomeThing(any(), any());
}

额外注意事项

  1. 同步方法的测试:methodA中的validating(obj)和someProcessing(obj, userId)是同步执行的,你可以单独测试这些逻辑,比如模拟validating抛出异常,验证methodA是否会正确处理(比如提前返回或抛出对应异常)。
  2. 私有方法的验证:如果convertToSomething是ServiceA的私有方法,不需要单独测试——通过验证objB.doSomeThing的参数是否符合预期,就能间接验证convertToSomething的逻辑是否正确。
  3. 参数匹配器的准确性:尽量用eq()匹配具体的参数值,而不是泛用的any(),这样验证结果会更准确,避免误判。

内容的提问来源于stack exchange,提问作者Abhishek Sarda

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 20:07:58