如何测试CompletableFuture.supplyAsync中Supplier传入的私有方法?
我需要为一个使用CompletableFuture.supplyAsync处理数据并保存到数据库的公共方法编写JUnit测试。该方法内部调用了私有方法processGrowthOverviewData,这个私有方法封装了数据处理和存库的核心逻辑。我希望在测试公共方法updateGrowthOverviewData的同时覆盖私有方法的逻辑,但不想通过修改getFuture的访问权限来Mock它,因为这样无法测试那些为提升可读性而编写的私有方法。
示例代码如下:
public void updateGrowthOverviewData(int startId, int endId){ int limit = 10; int currentStartId = startId; List<CompletableFuture<Boolean>> futureList = new ArrayList<>(); while (currentStartId <= endId) { long currentEndId = currentStartId + limit - 1; long finalCurrentStartId = currentStartId; futureList.add(this.getFuture(() -> { try { return processGrowthOverviewData(finalCurrentStartId, currentEndId); } catch (Exception ex) { log.error("Exception in updateGrowthOverviewData | while fetching data for updateGrowthOverviewData | startId : {}, endId : {} | Exception: {}", finalCurrentStartId, currentEndId, ex.getStackTrace()); return false; } })); currentStartId += limit; } futureList.forEach(obj -> { try { log.info("in updateGrowthOverviewData| value: {}", obj.get()); } catch (InterruptedException | ExecutionException ex) { log.error("Exception | in updateGrowthOverviewData | while fetching data from Future Object | Exception {}", ExceptionUtils.getStackTrace(ex)); } }); } private Boolean processGrowthOverviewData(int startId, int endId){ // 数据处理及数据库保存逻辑,包含其他私有方法 return true; } private <T> CompletableFuture<T> getFuture(Supplier<T> supplier) { return CompletableFuture.supplyAsync(supplier, this.serviceExecutor); }
1. 聚焦公共方法的行为测试(推荐)
单元测试的核心是验证公共方法的外部行为,而非内部私有方法的实现。既然processGrowthOverviewData的作用是处理并保存数据,那么测试updateGrowthOverviewData时,直接验证数据库的最终状态即可,这样自然覆盖了私有方法的逻辑:
- 测试前清空测试数据库的对应表
- 调用
updateGrowthOverviewData(startId, endId) - 查询数据库,验证是否生成了预期数量、内容正确的数据
- 同时可以验证日志输出是否符合预期(比如无错误日志,有正常的任务完成日志)
这种方式不需要修改原有代码,完全符合黑盒测试的思路,也能确保整个流程的正确性。
2. 用反射直接调用私有方法单独验证
如果需要单独测试processGrowthOverviewData的逻辑(比如验证数据处理规则),可以通过反射绕过私有访问限制:
@Test void testProcessGrowthOverviewData() throws Exception { // 获取目标类的实例 YourService service = new YourService(); // 获取私有方法 Method method = YourService.class.getDeclaredMethod("processGrowthOverviewData", int.class, int.class); // 取消访问检查 method.setAccessible(true); // 调用私有方法 Boolean result = (Boolean) method.invoke(service, 1, 10); // 验证结果 assertTrue(result); // 额外验证数据库数据是否正确 }
注意:反射会破坏封装性,仅建议在必要时使用,比如单独验证复杂的私有逻辑。
3. 替换Executor为同步执行器解决异步问题
原方法使用自定义的serviceExecutor执行异步任务,测试时如果异步执行会导致测试代码在任务完成前就结束,无法验证结果。可以通过依赖注入的方式,在测试时将serviceExecutor替换为同步执行器,让任务在当前线程执行:
// 在你的服务类中,确保serviceExecutor是可注入的(比如通过构造方法) public class YourService { private final Executor serviceExecutor; // 构造方法注入 public YourService(Executor serviceExecutor) { this.serviceExecutor = serviceExecutor; } // 原有方法... } // 测试类中 @Test void testUpdateGrowthOverviewData() { // 创建同步执行器,任务直接在当前线程运行 Executor syncExecutor = Runnable::run; YourService service = new YourService(syncExecutor); // 调用公共方法 service.updateGrowthOverviewData(1, 25); // 验证数据库数据、日志等 }
这种方式确保异步任务同步完成,测试逻辑更可靠,同时不需要修改原有方法的访问权限。
内容的提问来源于stack exchange,提问作者Gargi Chaurasia

