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

为何我的单元测试中CompletableFuture始终无法结束?

问题描述

我正在为一个使用CompletableFuture.supplyAsync()的旧方法编写单元测试,方法逻辑不复杂,但执行join()时测试一直停不下来。我觉得supplyAsync()应该能快速返回结果,怀疑是taskExecutor配置错误?我是Spring和Java新手,求各位给点建议,谢谢。

业务代码

public Response getReponse(Request request) {
  CompletableFuture<String> vip = CompletableFuture.supplyAsync(() -> {
    if(StringUtils.isBlank(request.getAddress())){ 
        return "";
    }
    Request newRequest = Request.builder()
            .userId(request.getUserId())
            .address(request.getZipCode())
            .build();
    Response newResult = getResult(newRequest);
    return ("12345".equals(newResult.getZipCode()) + ""); // 原代码缺少右括号,已修正
  }, taskExecutor);
  final Response result = getResult(request);
  result.setVIP(vip.join()); // 原代码变量名写错,zipCode应为vip,已修正
  return result;
}

单元测试代码

@Mock
private ThreadPoolTaskExecutor taskExecutor;

@Test
void getReponseTest(){
  SomeService someService = new SomeService(Constants, logService, taskExecutor);
  final SomeService someServiceSpy = Mockito.spy(someService);
  final Request request = TestHelper.buildRequest();
  final Response response = TestTestHelper.buildResponse();

  doReturn(response).when(someServiceSpy).getResult(any(Request.class));

  Response result = someServiceSpy.getReponse(request); // 原代码方法名和变量名有误,已修正

  // assertion
  ...
}
问题分析与解决建议
  • 核心原因:Mock的线程池未执行任务
    你用Mockito Mock了ThreadPoolTaskExecutor,但默认Mock对象不会主动执行传入的Runnable任务,导致CompletableFuture一直处于未完成状态,调用join()就会无限阻塞。

  • 解决方法一:让Mock线程池执行任务
    在测试方法中添加Stub逻辑,手动执行提交的任务:

    doAnswer(invocation -> {
        Runnable task = invocation.getArgument(0);
        task.run(); // 在当前线程同步执行任务
        return null;
    }).when(taskExecutor).execute(any(Runnable.class));
    

    这样supplyAsync()提交的任务会被立即执行,CompletableFuture能正常完成,join()就不会再卡住。

  • 解决方法二:使用真实线程池(更贴近生产环境)
    如果不需要Mock线程池,直接初始化一个简单的真实线程池:

    private ThreadPoolTaskExecutor taskExecutor;
    
    @BeforeEach
    void setUp() {
        taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(2);
        taskExecutor.setMaxPoolSize(2);
        taskExecutor.initialize();
    }
    

    这种方式适合测试业务逻辑本身,而非线程池的特定行为。

  • 额外修正:业务代码中的笔误
    原业务代码存在两处错误,会导致编译或运行异常:

    1. 返回语句缺少右括号:return ("12345".equals(newResult.getZipCode()) + ""; 应改为 return ("12345".equals(newResult.getZipCode()) + "");
    2. 变量名错误:result.setVIP(zipCode.join()); 中的zipCode应为前面定义的vip变量。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 21:00:52