如何为ListenableFuture回调的onSuccess和onFailure方法编写JUnit测试
ListenableFuture回调方法JUnit测试编写指引
你代码中的回调逻辑运行在独立的线程池futureCallBackExecutorService中,JUnit主线程默认不会等待异步任务执行完成就会结束用例,所以核心要解决异步逻辑的同步验证问题,以下是具体实现方案:
方案1:替换异步线程池为同步执行器(推荐)
实现原理
通过依赖注入将原Service类的异步线程池替换为Guava提供的MoreExecutors.newDirectExecutorService(),该执行器会让回调逻辑直接在当前线程同步执行,完全消除异步差异,无需额外等待逻辑,测试代码更简洁高效。
改造前提
优先调整Service类的构造函数,支持注入线程池,方便测试时替换:
class Service { private final ExecutorService futureCallBackExecutorService; // 生产环境注入实际的异步线程池,测试环境注入同步执行器 public Service(ExecutorService futureCallBackExecutorService) { this.futureCallBackExecutorService = futureCallBackExecutorService; } // 原有逻辑不变 public ResultSetFuture execute(Statement statement) { ResultSetFuture results = super.execute(statement); processCallBack(results); return results; } private void processCallBack(ResultSetFuture resultSetFuture) { Futures.addCallback(resultSetFuture, new FutureCallback<ResultSet>() { @Override public void onSuccess(@Nullable final ResultSet result) { // 业务逻辑 } @Override public void onFailure(@Nonnull final Throwable t) { // 异常处理逻辑 } }, this.futureCallBackExecutorService); } }
方案2:使用CountDownLatch等待异步执行
如果无法修改原有Service类的线程池注入逻辑,可以使用CountDownLatch作为同步工具,让JUnit主线程等待回调执行完成后再做验证。
实现原理
初始化计数为1的CountDownLatch,在回调逻辑的最后(可以通过Mockito的Answer拦截回调执行)调用countDown(),测试主线程调用await()设置超时时间等待计数归零后再验证结果。
完整测试示例(基于JUnit4 + Mockito)
import com.google.common.util.concurrent.MoreExecutors; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import static org.mockito.Mockito.*; @RunWith(MockitoJUnitRunner.class) public class ServiceTest { @Mock private Statement mockStatement; @Mock private ResultSet mockResultSet; // 测试时注入同步执行器,回调直接在当前线程执行 private final Service testService = new Service(MoreExecutors.newDirectExecutorService()); @Test public void test_onSuccess_logic() { // 1. 构造成功的ResultSetFuture ResultSetFuture successFuture = Futures.immediateFuture(mockResultSet); // 2. Mock父类execute方法返回构造好的成功Future doReturn(successFuture).when(testService).execute(mockStatement); // 3. 执行目标方法 testService.execute(mockStatement); // 4. 直接验证onSuccess内的业务逻辑是否正确执行,比如回调内调用了某依赖的save方法 // verify(mockDependency, times(1)).save(mockResultSet); } @Test public void test_onFailure_logic() { // 1. 构造失败的ResultSetFuture,指定异常 RuntimeException testException = new RuntimeException("测试异常"); ResultSetFuture failedFuture = Futures.immediateFailedFuture(testException); // 2. Mock父类execute方法返回构造好的失败Future doReturn(failedFuture).when(testService).execute(mockStatement); // 3. 执行目标方法 testService.execute(mockStatement); // 4. 直接验证onFailure内的异常处理逻辑是否正确执行 // verify(mockErrorHandler, times(1)).handleException(testException); } }
注意事项
- 异步测试必须设置超时时间,避免任务卡住导致整个测试流程阻塞
- 回调逻辑依赖的外部组件要提前完成Mock,执行后验证调用次数、入参是否符合预期
- 优先使用同步执行器替换的方案,测试执行效率更高,代码复杂度更低
内容的提问来源于stack exchange,提问作者ssingh
相关产品推荐
相关产品推荐

