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

集成测试中如何Mock CompletableFuture.join()抛出异常

搞定CompletableFuture.join()的Mock异常问题

问题出在哪

你现在的Mock写法有两个关键问题:

  1. Mock的对象根本没被用到:你用@MockBean创建的CompletableFuture实例,和业务代码里callCarrierPost返回的CompletableFuture.completedFuture()完全不是同一个对象,业务逻辑处理的是后者,所以你的Mock等于白写。
  2. 异常Mock的方式错了:你用thenReturn返回一个异常对象,但join()需要的是抛出异常,而且CompletableFuture.join()抛出的是**CompletionException**,实际异常会被包在这个异常的cause里,不是直接返回异常对象。

正确的解决办法

根据你的代码逻辑,有两种靠谱的Mock方式:

方法1:让callCarrierPost返回带异常的Future

直接Mock业务类的callCarrierPost方法,让它返回一个已经处于异常状态的CompletableFuture,这样调用join()时会自动抛出CompletionException。

@Autowired
private YourService yourService; // 替换成你的业务类名称

@Test
void testProviderTimeoutCase() {
    // 创建一个失败的Future,包裹SocketTimeoutException
    CompletableFuture<ResponseEntity<Object>> failedFuture = new CompletableFuture<>();
    failedFuture.completeExceptionally(
        new ResourceAccessException("I/O error on /uri", new SocketTimeoutException("Read Timeout"))
    );

    // Mock callCarrierPost方法返回这个异常Future
    when(yourService.callCarrierPost(any(ServiceConfig.class), any(Class.class), any(ExecutionContext.class), any(AdapterContext.class)))
        .thenReturn(failedFuture);

    // 构造Future列表并处理
    List<CompletableFuture<ResponseEntity<Object>>> futureList = Collections.singletonList(failedFuture);
    List<ResponseEntity<Object>> result = yourService.processResponseFutureList(futureList);

    // 验证结果:应该返回SERVICE_UNAVAILABLE的错误响应
    assertThat(result.get(0).getStatusCode()).isEqualTo(HttpStatus.SERVICE_UNAVAILABLE);
}

方法2:Mock底层的carrierInvoker抛出异常

因为callCarrierPost里是同步调用carrierInvoker.postForObject后返回completedFuture,所以直接MockcarrierInvoker抛出异常,这样callCarrierPost返回的Future自然就带异常了。

@MockBean
private CarrierInvoker carrierInvoker; // 你的carrierInvoker类

@Autowired
private YourService yourService;

@Test
void testHttpErrorCase() {
    // Mock carrierInvoker抛出HttpStatusCodeException
    when(carrierInvoker.postForObject(any(ServiceConfig.class), any(Class.class), any(ExecutionContext.class), any(AdapterContext.class)))
        .thenThrow(new HttpClientErrorException(HttpStatus.BAD_REQUEST, "Bad Request"));

    // 调用方法获取Future(传入对应参数)
    CompletableFuture<ResponseEntity<Object>> future = yourService.callCarrierPost(new ServiceConfig(), Object.class, new ExecutionContext(), new AdapterContext());

    // 处理Future列表
    List<ResponseEntity<Object>> result = yourService.processResponseFutureList(Collections.singletonList(future));

    // 验证结果:返回BAD_REQUEST的响应
    assertThat(result.get(0).getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
}

重要提醒

  • CompletableFuture.completeExceptionally(Throwable)是让Future进入异常状态的正确方式,调用join()时会自动抛出包裹了该异常的CompletionException,正好匹配你processResponseFutureList里的异常处理逻辑。
  • 别想着直接MockCompletableFuture的join()方法,因为CompletableFuture是final类(JDK8及以上),Mockito默认Mock不了final方法,就算开了额外配置也没必要,上面两种方法更直接简单。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 13:06:17