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

如何为CompletableFuture.runAsync内的业务逻辑编写JUnit测试

问题根因

CompletableFuture.runAsync() 默认会把异步任务提交到JVM全局的ForkJoinPool.commonPool()执行,你的测试主线程调用完loan.writeIntoCassandra()方法后会直接继续执行后续逻辑、结束测试流程,JVM随之退出,此时异步任务还没被调度执行,自然不会命中内部逻辑、也不会调用两个私有写入方法。

注意测试代码的方法名拼写,业务方法为writeIntoCassandra,你的示例测试中写为writeIntoCassendra(少字母a),也会导致调用异常

可行解决方案
  • 方案1:改造业务代码返回异步任务实例,测试侧显式等待任务完成
    这是最稳妥的方案,对生产逻辑侵入极小,测试可靠性最高。

改造后的业务代码示例:

public class Loan{
    private long startTime = System.currentTimeMillis();
    // 生产环境保持原有返回逻辑,新增包级测试方法返回异步任务聚合结果
    public Transaction writeIntoCassandra(Transaction transaction){
        writeIntoCassandraAsync(transaction);
        return transaction;
    }

    // 包访问权限,仅测试用
    CompletableFuture<Void> writeIntoCassandraAsync(Transaction transaction) {
        CompletableFuture<Void> writeTask1 = CompletableFuture.runAsync(() -> {
            try{
                writeIntoCassandraTable(transaction);
            } catch(JsonConversionException e){
                // 建议至少加日志,不要空吞异常
            }         
        });
        CompletableFuture<Void> writeTask2 = CompletableFuture.runAsync(() -> {
            try{
                writeIntoCassandraTable2(transaction);
            } catch(JsonConversionException e){
            }         
        });
        return CompletableFuture.allOf(writeTask1, writeTask2);
    }

    private void writeIntoCassandraTable(Transaction t) {
        // 原有逻辑
    }
    private void writeIntoCassandraTable2(Transaction t) {
        // 原有逻辑
    }
}

测试侧改造:

@InjectMocks
private Loan loan;

@Test
public void writeIntoCassandraTest() throws Exception {
  String jsonTransaction = "你的测试业务JSON";
  ObjectMapper objectMapper = new ObjectMapper();
  Transaction transaction = objectMapper.readValue(jsonTransaction, Transaction.class);
  // 等待所有异步任务执行完成
  loan.writeIntoCassandraAsync(transaction).join();
  // 此处执行断言验证逻辑正确性即可
}
  • 方案2:依赖注入Executor,测试时替换为同步执行器
    把异步执行的线程池作为依赖注入到Loan类中,测试时传入一个直接同步运行任务的Executor,不需要异步调度,自然就能命中内部逻辑。

业务代码改造:

public class Loan{
    private long startTime = System.currentTimeMillis();
    // 注入线程池,生产环境注入ForkJoinPool.commonPool()或者自定义线程池
    private final Executor executor;

    public Loan(Executor executor) {
        this.executor = executor;
    }

    public Transaction writeIntoCassandra(Transaction transaction){
        CompletableFuture.runAsync(() -> {
            try{
                writeIntoCassandraTable(transaction);
            } catch(JsonConversionException e){}         
        }, executor); // 用注入的线程池执行
        CompletableFuture.runAsync(() -> {
            try{
                writeIntoCassandraTable2(transaction);
            } catch(JsonConversionException e){}         
        }, executor);
        return transaction;
    }
}

测试侧代码:

// 测试用同步执行器,直接在调用线程运行任务
private static final Executor SYNC_EXECUTOR = Runnable::run;

@InjectMocks
private Loan loan = new Loan(SYNC_EXECUTOR);

@Test
public void writeIntoCassandraTest() throws Exception {
  String jsonTransaction = "你的测试业务JSON";
  ObjectMapper objectMapper = new ObjectMapper();
  Transaction transaction = objectMapper.readValue(jsonTransaction, Transaction.class);
  loan.writeIntoCassandra(transaction);
  // 异步任务已经同步执行完成,直接断言即可
}
  • 方案3:测试侧使用等待工具判断异步逻辑执行完成
    如果不想改动业务代码,可以用Awaitility工具轮询判断写入方法是否被调用,适合无法修改业务代码的场景:
@Test
public void writeIntoCassandraTest() throws Exception {
  String jsonTransaction = "你的测试业务JSON";
  ObjectMapper objectMapper = new ObjectMapper();
  Transaction transaction = objectMapper.readValue(jsonTransaction, Transaction.class);
  loan.writeIntoCassandra(transaction);
  // 最多等待3秒,直到异步逻辑执行完成符合断言条件
  Awaitility.await().atMost(Duration.ofSeconds(3)).untilAsserted(() -> {
      // 此处写断言逻辑,比如验证Mock的Cassandra操作是否被调用
  });
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 02:36:08