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

测试CompletableFuture get() catch块失败,如何正确编写测试用例?

如何覆盖私有方法中CompletableFuture的catch代码块?

问题描述

我需要为代码中私有方法的catch代码块编写测试用例:
调用的public方法如下:

public void addValidationResultToValidations(Instrument instrument, AssetDetail assetDetail) {
    List<ValidationResult> validationResults = getValidationResults(instrument, assetDetail);
    // .... some other logic
}

其中getValidationResults是返回ValidationResult列表的私有方法,实现如下:

private List<ValidationResult> getValidationResults(Instrument instrument, AssetDetail assetDetail) {
    List<CompletableFuture<ValidationResult>> completableFutureList = new ArrayList<>(); // 此处实际是调用服务生成的Future列表
    CompletableFuture<List<ValidationResult>> listCompletableFuture = CompletableFuture.allOf(completableFutureList.toArray(new CompletableFuture[completableFutureList.size()]))
            .thenApply(v -> completableFutureList.stream()
                    .filter(Objects::nonNull)
                    .map(CompletableFuture::join)
                    .collect(Collectors.toList()));
    try {
        return listCompletableFuture.get();
    } catch (InterruptedException | ExecutionException e) {
        System.out.println("exception {}"+ e);
    }
    return new ArrayList<>();
}

我编写的测试用例尝试触发异常以覆盖catch块,但报错:

org.opens4j.AssertionError: Expected java.lang.Exception to be thrown, but nothing was thrown

测试用例代码:

@Test
public void whenGetIsCalledOnCompletableFutureThenThrowException(){
    Instrument instrument = AssetUtils.getInstrument();
    AssetDetail assetDetail = AssetUtils.getAssetDetail();
    CompletableFuture<List<ValidationResult>> badFuture = new CompletableFuture<>();
    badFuture.completeExceptionally(new Exception());
    assertThrown(Exception.class, () -> {
         service.addValidationResultToValidations(instrument, assetDetail);
    })
}

错误原因

  1. 测试用例中的badFuture未被业务代码使用:你创建的异常Future完全独立于业务逻辑,业务代码里的completableFutureList要么是空列表,要么是由实际服务调用生成的正常Future,所以listCompletableFuture.get()不会抛出异常。
  2. 业务方法不会向外抛出异常:即使get()抛出异常,私有方法的catch块已经捕获了异常,只会打印日志并返回空列表,上层的addValidationResultToValidations方法不会把异常抛出去,所以你断言“会抛出Exception”必然失败。

解决方案

要覆盖catch块,需要让listCompletableFuture.get()抛出InterruptedException或ExecutionException,同时调整测试断言逻辑(因为上层方法不会抛异常)。推荐通过mock生成CompletableFuture的依赖服务来实现:

步骤1:Mock依赖服务,返回异常的CompletableFuture

假设completableFutureList是由某个服务(比如ValidationService)的方法生成的,用Mockito mock该服务,让它返回一个会抛出异常的CompletableFuture:

@Mock
private ValidationService validationService;

@InjectMocks
private YourService service; // 你的业务服务类

@Test
public void whenValidationFutureFails_thenCatchBlockIsTriggered() {
    Instrument instrument = AssetUtils.getInstrument();
    AssetDetail assetDetail = AssetUtils.getAssetDetail();

    // 让mock服务返回一个异常的CompletableFuture
    CompletableFuture<ValidationResult> failedFuture = new CompletableFuture<>();
    failedFuture.completeExceptionally(new RuntimeException("Validation failed"));
    when(validationService.getValidationFuture(any(Instrument.class), any(AssetDetail.class)))
            .thenReturn(failedFuture);

    // 调用public方法
    service.addValidationResultToValidations(instrument, assetDetail);

    // 验证catch块的逻辑:比如验证日志是否打印(可借助日志框架的测试工具,如Logback的Appender)
    // 或者验证后续逻辑是否基于空列表执行,比如验证validations是否为空
    // 示例:如果addValidationResultToValidations会把结果存入某个集合,可断言该集合为空
    // assertThat(service.getValidations()).isEmpty();
}

步骤2:验证catch块的执行

因为catch块里只做了日志打印,你可以用日志测试工具(如org.slf4j:slf4j-test)来验证日志是否输出了异常信息,确保catch块被执行。

备选方案:反射调用私有方法

如果无法mock依赖服务,也可以用反射直接调用私有方法,同时构造让get()抛出异常的场景:

@Test
public void whenGetThrowsException_thenCatchBlockIsExecuted() throws Exception {
    Instrument instrument = AssetUtils.getInstrument();
    AssetDetail assetDetail = AssetUtils.getAssetDetail();

    // 反射获取私有方法
    Method method = YourService.class.getDeclaredMethod("getValidationResults", Instrument.class, AssetDetail.class);
    method.setAccessible(true);

    // 构造包含异常Future的列表,让join()触发ExecutionException
    List<CompletableFuture<ValidationResult>> futureList = new ArrayList<>();
    CompletableFuture<ValidationResult> failedFuture = new CompletableFuture<>();
    failedFuture.completeExceptionally(new RuntimeException("Test exception"));
    futureList.add(failedFuture);

    // 如果你能通过反射替换业务方法中的completableFutureList,或者借助PowerMock模拟CompletableFuture的行为
    // 执行私有方法并验证返回结果为空列表
    List<ValidationResult> result = (List<ValidationResult>) method.invoke(service, instrument, assetDetail);
    assertThat(result).isEmpty();
}

注意事项

  • 不要断言上层方法抛出异常,因为私有方法已经捕获了异常并返回空列表,上层方法不会向外抛异常。
  • 优先通过mock依赖服务的方式构造异常场景,这更符合单元测试的原则,避免依赖反射或PowerMock这类侵入性工具。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 09:08:18