掌握JS异步测试关键在于理解异步行为如何被观测:需厘清Promise生命周期、事件循环调度及测试工具拦截机制,再针对Promise、定时器、fetch、async/await等场景选用对应验证策略与工具能力。

掌握 JS 异步代码测试,关键不在背 API,而在理解“异步行为如何被观测”——测试的本质是验证状态与时机。只要理清 Promise 生命周期、事件循环调度、以及测试工具的拦截机制,就能稳住节奏,不被时序问题带偏。
搞懂测试对象的异步本质
别急着写 test(),先确认你测的是什么:
- Promises:关注 resolve/reject 的值、时机、是否被 catch;注意 .then() 是微任务,不能用同步断言直接抓结果
- setTimeout / setInterval:属于宏任务,需用 fake timers(如 Jest 的 jest.useFakeTimers())控制时间流,否则测试会变慢或不稳定
- fetch / API 调用:实际网络请求必须 mock,推荐使用 msw(Mock Service Worker)或 Jest 的 mockImplementation,避免依赖后端、超时或跨域
- async/await 函数:它只是语法糖,底层仍是 Promise,测试方式和普通 Promise 一致,但要用 await 或 .then() 获取结果
用对测试工具的核心能力
主流框架(Jest、Vitest)已内置异步支持,重点用好三类机制:
-
返回 Promise:测试函数 return 一个 Promise,框架会自动等待它 settle。例如:
it('fetches user', () => expect(fetchUser()).resolves.toEqual({...})) -
async/it:直接写
it('handles error', async () => { await expect(fetchUser()).rejects.toThrow() }),语义清晰,调试友好 -
fake timers:调用
jest.useFakeTimers()后,用jest.runAllTimers()或jest.advanceTimersByTime(1000)主动推进时间,精准验证延时逻辑
覆盖典型异步场景的验证模式
真实项目里高频出现的几类逻辑,有固定测试范式:
- 加载中状态 → 成功/失败态:mock 返回不同 Promise 状态,断言 UI 元素(如 loading spinner 显示/隐藏、error message 是否出现)
- 竞态条件(Race Condition):比如快速切换 tab 触发两个请求,后发先至应被忽略。测试时 mock 两个延迟不同的 fetch,验证最终 state 只响应第一个完成的
- 重试逻辑:用 msw 模拟前几次失败(500)、最后一次成功,配合 fake timers 控制间隔,断言请求次数和最终数据
- 清理副作用(如取消请求):测试组件卸载时是否调用 abortController.abort(),可用 jest.spyOn(AbortController.prototype, 'abort') 验证
避开高频陷阱
这些细节不注意,测试看似通过,实则无效:
- 忘记
await或returnPromise → 测试提前结束,断言永远不执行 - 用
console.log查看异步结果 → 它在微任务队列里,可能比测试结束还晚,看不到输出 - mock 了 fetch 却没处理
response.json()→ 它本身也返回 Promise,需链式 mock 或 await 两层 - 在 beforeEach 里启动定时器但没清理 → 泄露到下一个测试,造成干扰


















