CompletableFuture单元测试遇NPE:Mockito Stub未生效求助
问题分析与解决方案
核心问题定位
你遇到的Stub失效、空指针异常(NPE)问题,大概率是以下两个关键原因:
- Stub返回值类型不匹配:你错误地Stub返回了
CompletableFuture,但firstHandler.handle实际返回POJO、secondHandler.handle返回基本类型double,Mockito会判定方法签名不匹配,直接返回默认值(引用类型为null,基本类型double为0.0)。 - Mock对象未正确注入:
RateHandler中调用的firstHandler/secondHandler不是你通过Mockito创建的Mock实例,而是方法内部新建的对象,导致Stub逻辑根本没被触发。
分步解决方案
1. 修正Stub写法(匹配方法真实返回类型)
针对不同返回类型的handler方法,写出正确的Stub:
- 对于返回POJO的
firstHandler.handle:// 构造测试用的POJO实例 RateResponse mockRateResp = new RateResponse(); // 确保参数匹配(用any()或具体参数,根据你的方法参数调整) when(firstHandler.handle(any(YourParamType.class))).thenReturn(mockRateResp); - 对于返回基本类型
double的secondHandler.handle:
基本类型的Stub要注意自动装箱/拆箱的匹配问题,推荐用doReturn写法更稳妥:
❌ 错误示例:不要返回doReturn(15.0).when(secondHandler).handle(any(YourParamType.class)); // 或者用thenReturn,注意传入基本类型而非包装类 when(secondHandler.handle(any(YourParamType.class))).thenReturn(15.0);CompletableFuture,比如when(secondHandler.handle(...)).thenReturn(CompletableFuture.completedFuture(15.0)),这完全不符合方法的返回类型,Stub会直接失效。
2. 确保Mock对象正确注入到RateHandler
检查RateHandler的实现,确保firstHandler和secondHandler是通过构造方法、Setter或依赖注入框架注入的,而非在firstMethod内部直接new实例:
// 正确的RateHandler实现(依赖注入) public class RateHandler { private final FirstHandler firstHandler; private final SecondHandler secondHandler; // 通过构造方法注入依赖 public RateHandler(FirstHandler firstHandler, SecondHandler secondHandler) { this.firstHandler = firstHandler; this.secondHandler = secondHandler; } public CompletableFuture<Result> firstMethod(YourParamType param) { // 这里调用的是注入的mock实例,而非新创建的对象 CompletableFuture<RateResponse> rateRespCf = CompletableFuture.supplyAsync(() -> firstHandler.handle(param)); CompletableFuture<Double> avgRateCf = CompletableFuture.supplyAsync(() -> secondHandler.handle(param)); // ...后续逻辑 } }
测试类中,要将Mockito创建的Mock实例传入RateHandler:
@ExtendWith(MockitoExtension.class) public class RateHandlerTest { @Mock private FirstHandler firstHandler; @Mock private SecondHandler secondHandler; // 注入Mock对象到被测类 @InjectMocks private RateHandler rateHandler; @Test public void testFirstMethod() throws Exception { // 写Stub逻辑... CompletableFuture<Result> result = rateHandler.firstMethod(testParam); // 等待异步任务完成 Result finalResult = result.get(); // 断言逻辑... } }
3. 处理异步任务的执行等待
如果测试中出现异步任务未执行完就进入断言的情况,导致拿到未完成的Future结果,可以:
- 调用
CompletableFuture.join()或get()等待任务完成(注意处理InterruptedException); - 给
supplyAsync指定单线程池,确保异步任务同步执行,比如在测试时注入自定义线程池:
测试时传入单线程池:// 在RateHandler中添加带线程池的构造方法 public RateHandler(FirstHandler firstHandler, SecondHandler secondHandler, Executor executor) { this.firstHandler = firstHandler; this.secondHandler = secondHandler; this.executor = executor; } // firstMethod中使用注入的线程池 CompletableFuture<RateResponse> rateRespCf = CompletableFuture.supplyAsync(() -> firstHandler.handle(param), executor);@Test public void testFirstMethod() throws Exception { Executor testExecutor = Executors.newSingleThreadExecutor(); RateHandler testRateHandler = new RateHandler(firstHandler, secondHandler, testExecutor); // ...后续测试逻辑 }
4. 排查Stub的参数匹配问题
如果Stub仍然失效,检查方法参数的匹配是否正确:
- 如果
handle方法的参数是复杂对象,不要用具体值匹配,改用any()、eq()等匹配器; - 确保参数的类型匹配,比如方法参数是
Long,不要用anyInt()匹配。
内容的提问来源于stack exchange,提问作者sai23
相关产品推荐
相关产品推荐

