JS异步测试核心是避免“测了白测”,需确保异步逻辑真实触发、精准控制时序、覆盖并发/错误/中间态等关键路径,并严格mock依赖与捕获未处理拒绝。

写 JS 异步代码的测试,核心不是“怎么测”,而是“怎么避免测了也白测”。很多测试看似跑通,实际对并发、时序、错误传播这些关键路径毫无覆盖,上线后照样翻车。关键在于理解异步行为在测试环境中的真实表现,并针对性地控制执行节奏和状态边界。
测试前先确认异步行为是否真正被触发
很多测试失败,根本原因在于异步逻辑压根没运行——比如忘记 await、漏掉 return Promise、或用了未 mock 的定时器/网络请求。测试中必须显式等待异步完成,不能依赖 setTimeout 模拟延时来“等结果”。
- 用 async test 函数 + await 包裹被测调用,确保测试框架识别异步流程
- 被测函数返回 Promise?测试里一定要 await 它 或 return 它,否则 Jest/Jasmine 会认为测试已结束
- 检查是否意外触发真实 API:所有 fetch / axios / setTimeout 应提前 mock,否则测试不可靠、不稳定、还慢
区分串行与并行场景,验证不同执行顺序
异步循环、Promise.all、Promise.allSettled 这些操作的行为差异极大,但测试常只覆盖“全部成功”的理想路径。实际要重点验证:
- 串行执行(for...of + await):确保第 2 项确实在第 1 项 resolve 后才发起,中间错误能中断后续
- 并行执行(Promise.all):模拟某一项 reject,验证整个调用是否被 catch,且不掩盖其他项的成功结果(需配合 allSettled 对比)
- 竞态条件(race):mock 多个延迟不同的 Promise,验证是否真按最快完成者返回
错误处理必须覆盖“拒绝未捕获”和“异常逃逸”
async/await 中 try/catch 能捕获 Promise reject,但无法捕获未 await 的 Promise 内部抛出的异常(如 setTimeout 里的 throw)。这类错误会变成 unhandledrejection,测试中必须主动监听并断言。
- 在测试开始前加 jest.useFakeTimers() 并配合 jest.runAllTimers(),防止定时器干扰
- 用 process.on('unhandledRejection', ...) 或 Jest 的 expect.addEqualityTesters 捕获未处理拒绝
- 对每个可能抛错的 await 行,单独写一个测试用例:传入 mock reject 的依赖,验证 error message 和类型是否符合预期
Mock 异步依赖要还原真实状态流转
只 mock 返回值远远不够。真实异步操作有三阶段:pending → fulfilled/rejected → clean up(如取消请求、释放资源)。测试需覆盖中间态影响。
- 用 jest.fn().mockReturnValue(Promise.resolve(...)) 控制 resolve 值;用 mockRejectedValue 控制 reject 值
- 需要验证“pending 期间用户点击取消”?mock 一个手动 resolve/reject 的 Promise,并在测试中先 await 一部分,再调用 cancel 方法
- 避免全局 mock 影响其他测试:每个 test 用 beforeEach(() => { jest.clearAllMocks() })











