如何编写单元测试验证Java中methodA异步调用methodB一次?
问题:如何验证调用methodA时异步执行的methodB被调用一次?
我需要验证:调用ClassA的methodA时,代码会通过异步方式执行methodB一次。代码示例如下:
public class ClassA { @Inject @Named("executor") ExecutionContextExecutor eventHandlerExecutor; public Object methodA (Object x, Object y) { ... CompletableFuture.runAsync(() -> methodB(i, j), eventHandlerExecutor); ... } public void methodB (Object n, Object m) { ... } }
我尝试了下面的测试代码,想问用Mockito的Spy能不能实现验证?
classA.methodA(x, y); // can spying classA help to verify it? verify(classA, times(1)).methodB(any(), any());
回答
用Mockito的Spy是可以实现验证的,但要解决异步代码测试的两个核心问题:异步任务的执行时机、测试线程的等待逻辑。下面是具体的实现方案:
1. 正确创建Spy对象
首先要确保用Mockito的spy()方法创建ClassA的代理对象,而非直接new实例。同时要给Spy对象注入可用的eventHandlerExecutor——如果用真实线程池,会导致测试线程和异步线程不同步,直接verify会失败。推荐替换成同步执行器,让异步任务在测试线程里同步执行,避免等待问题:
// 创建ClassA的Spy对象 ClassA classA = Mockito.spy(new ClassA()); // 注入同步执行器:直接在当前线程执行任务,相当于把异步变成同步 classA.eventHandlerExecutor = Runnable::run;
2. 执行测试并验证
此时调用methodA后,methodB会立刻在测试线程里执行,直接verify就能生效:
// 调用目标方法 classA.methodA(x, y); // 验证methodB被调用1次,参数匹配任意Object Mockito.verify(classA, Mockito.times(1)).methodB(Mockito.any(), Mockito.any());
3. 如果必须保留异步执行(不替换executor)
如果测试场景需要保留真实的异步逻辑,可以用超时验证或者CountDownLatch来等待异步任务完成:
方式一:超时验证
Mockito支持设置超时时间,等待异步任务执行完毕后再验证:
ClassA classA = Mockito.spy(new ClassA()); // 注入真实的executor或者测试用线程池 classA.eventHandlerExecutor = yourTestExecutor; classA.methodA(x, y); // 设置1秒超时,确保异步任务有足够时间执行 Mockito.verify(classA, Mockito.timeout(1000).times(1)).methodB(Mockito.any(), Mockito.any());
方式二:用CountDownLatch手动等待
在测试中添加CountDownLatch,修改Spy的methodB方法来触发计数:
CountDownLatch latch = new CountDownLatch(1); ClassA classA = Mockito.spy(new ClassA()); // 注入真实executor classA.eventHandlerExecutor = yourTestExecutor; // 拦截methodB的调用,执行原逻辑后触发latch Mockito.doAnswer(invocation -> { Object result = invocation.callRealMethod(); latch.countDown(); return result; }).when(classA).methodB(Mockito.any(), Mockito.any()); classA.methodA(x, y); // 等待最多1秒,直到latch计数为0 latch.await(1, TimeUnit.SECONDS); // 验证调用次数 Mockito.verify(classA, Mockito.times(1)).methodB(Mockito.any(), Mockito.any());
注意事项
- 确保
methodB是public方法:Spy只能拦截public/protected方法,private方法无法直接verify(如果是private,需要用PowerMock或者重构代码) - 测试用的executor要避免线程泄漏:比如用
Executors.newSingleThreadExecutor()后要记得关闭,或者用框架提供的测试专用executor
内容的提问来源于stack exchange,提问作者Luka
相关产品推荐
相关产品推荐

