You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Stubbing @Async方法后同步执行,超时测试失败问题排查

我来帮你分析下问题所在以及对应的解决办法:

问题根源分析

你遇到的核心问题在于Mock Stub返回的AsyncResult是一个「已完成状态的Future」,和真实@Async方法返回的未完成Future行为完全不同:

  1. 真实场景下,@Async注解通过Spring AOP生成代理对象,调用asyncMethod()时会把任务提交到异步线程池执行,返回的Future会在后台线程完成前一直处于未完成状态。所以当你调用futureResult.get(2, TimeUnit.SECONDS)时,主线程等待2秒后发现任务还没结束,就会抛出TimeoutException。
  2. 但在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 07:23:39