Stubbing @Async方法后同步执行,超时测试失败问题排查
我来帮你分析下问题所在以及对应的解决办法:
问题根源分析
你遇到的核心问题在于Mock Stub返回的AsyncResult是一个「已完成状态的Future」,和真实@Async方法返回的未完成Future行为完全不同:
- 真实场景下,
@Async注解通过Spring AOP生成代理对象,调用asyncMethod()时会把任务提交到异步线程池执行,返回的Future会在后台线程完成前一直处于未完成状态。所以当你调用futureResult.get(2, TimeUnit.SECONDS)时,主线程等待2秒后发现任务还没结束,就会抛出TimeoutException。 - 但在Mock测试中,你通过
thenAnswer返回的new AsyncResult<>(0)有个关键特性:它一旦被创建就处于已完成状态(isDone()直接返回true)。哪怕你在thenAnswer里加了Thread.sleep(5000),这段sleep也是在主线程里执行的(因为MockBean不会触发Spring的异步代理逻辑),等sleep结束后返回的Future已经是完成状态,调用get(2秒)会立刻拿到结果,自然不会触发超时。
解决方案:返回未完成的Future
要模拟真实的异步超时场景,你需要在Stub时返回一个未完成的Future,让它在后台线程延迟完成。推荐用CompletableFuture来实现,修改测试类的Stub逻辑:
when(a.asyncMethod()).thenAnswer(invocation -> { // 创建一个初始未完成的CompletableFuture CompletableFuture<Integer> future = new CompletableFuture<>(); // 启动新线程执行耗时任务,5秒后完成Future new Thread(() -> { try { Thread.sleep(5000); future.complete(0); } catch (InterruptedException e) { future.completeExceptionally(e); } }).start(); return future; });
这样修改后,调用a.asyncMethod()会立刻返回一个未完成的Future,后台线程开始执行5秒的sleep。主线程调用future.get(2秒)时,等待2秒后任务仍未完成,就会如期抛出TimeoutException。
额外小提示
- 不要用
AsyncResult模拟未完成的异步任务,它的设计初衷就是用来表示「已经完成的异步结果」。 - 测试中如果需要更规范地管理线程,可以用
ExecutorService来提交任务,避免手动创建线程的零散写法。
内容的提问来源于stack exchange,提问作者Elad Cohen
相关产品推荐
相关产品推荐

