JS异步测试需按“异步认知→工具原理→场景验证→稳健加固”四步推进:先理解微任务/宏任务时序,再掌握return Promise、async/await、done三种写法适用场景,接着针对定时器、接口请求、事件混合三类高频问题用fake timers、MSW、waitFor等方案解决,最后通过禁用真实网络和定时器、避免setTimeout等待来提升稳定性。

JS 异步代码测试不是单纯学断言或写 it 用例,而是要先理解异步行为本身——否则你写的测试可能“看起来通过”,实则漏测、误判、时序错乱。系统学习的关键是:按“异步认知 → 测试工具原理 → 场景化验证 → 稳健性加固”四步推进,每步都紧扣执行机制。
先搞懂异步行为在测试中怎么“露馅”
很多测试失败不是代码错,是没意识到 JS 异步的执行节奏。比如:
- setTimeout 或 Promise.then 没等完就断言:测试跑太快,断言发生在回调之前,结果永远是初始值;
-
多个 Promise 并发时状态交错:比如两个
fetch返回不同数据,但测试只 mock 一个响应,另一个走真实网络或 fallback; -
微任务 vs 宏任务顺序被忽略:
Promise.resolve().then()总比setTimeout(() => {}, 0)先执行,测试里若混用却没同步等待,结果不可控。
建议用最简代码在控制台反复验证执行顺序(如输出数字 + 标签),再把同一逻辑搬到测试中观察差异。这是所有后续测试可靠的前提。
掌握 Jest / Vitest 中异步测试的三种写法及适用场景
不是“会写 async/await 就算会测异步”,而是要清楚每种写法背后如何与事件循环协同:
-
返回 Promise:测试函数直接 return 一个 Promise,Jest 会自动 await 它完成。适合单个异步操作,如
return api.getUser(id); -
使用 async/await:函数声明为
async,内部用await等待异步调用。适合多步链式调用,如await login(); const user = await getUser(); expect(user).toBeDefined();; -
done 回调(已不推荐):传入
done参数,手动调用done()或done(err)。仅用于无法返回 Promise 的老式回调 API,现代代码尽量避免。
注意:Jest 默认超时是 5 秒,复杂异步流程可加 jest.setTimeout(10000),但更应优先考虑是否可 mock 或简化依赖。
真实项目中最常卡壳的三类异步测试场景
光会语法不够,得知道在哪种业务逻辑里容易翻车:
-
定时器驱动逻辑(如轮询、防抖):不要真等 3 秒,用
jest.useFakeTimers()控制时间流逝,再用jest.runAllTimers()或jest.advanceTimersByTime(2000)精确触发; -
接口请求(fetch / axios):禁用真实网络(
jest.mock('node-fetch')或用msw拦截),统一返回预设响应,避免环境差异和网络波动干扰; -
事件监听与异步响应混合:例如组件监听
customEvent,事件处理中发起请求并更新状态。测试需分两步:先触发事件,再等待异步更新完成(可用await waitFor(() => expect(...).toBeInTheDocument()))。
让异步测试真正可靠的两个加固习惯
多数人写了测试就以为万事大吉,但异步测试极易“幸存偏差”——只在本地快网环境通过,CI 上随机失败:
- 禁用真实定时器和网络是默认动作:在测试配置或每个 describe 前统一启用 fake timers 和 mock fetch,而不是“需要时才加”;
-
不用
setTimeout等待,改用waitFor或findBy*:前者基于条件轮询(最多 1s),后者自带重试,语义清晰且稳定。例如不用await new Promise(r => setTimeout(r, 100)),而用await findByText('加载完成')。
不复杂但容易忽略。











