如何为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
相关产品推荐
相关产品推荐

