学js异步测试关键在于建立“可控异步+显式断言+边界覆盖”思维,需mock所有异步源头、分阶段断言生命周期,并覆盖超时、取消、竞态、错误传播四大边界场景,同时规避timeout设置、done回调、act包裹和mock清理等常见陷阱。

学 JS 异步代码测试,关键不是堆砌工具用法,而是建立“可控异步 + 显式断言 + 边界覆盖”的思维习惯。正常走通只是起点,真正要练的是把超时、竞态、取消、中间状态这些容易出错的环节,变成可重复验证的测试用例。
用 mock 拆掉真实异步依赖
不调真实接口、不等真实定时器——这是健壮测试的第一条铁律。所有异步源头(fetch、axios、setTimeout、Promise 构造函数)都该被替换为可控行为:
- 立即成功:
jest.fn(() => Promise.resolve(data)) - 延迟成功:
jest.fn(() => new Promise(r => setTimeout(() => r(data), 100))) - 立即失败:
jest.fn(() => Promise.reject(new Error('network error'))) - 延迟失败:
jest.fn(() => new Promise((_, rej) => setTimeout(() => rej(new Error('timeout')), 50)))
分阶段断言完整生命周期
别只看最终结果。一个 loading 状态切换,至少有三个关键节点需要确认:
- 发起请求前:loading 为 false,data 为空,error 为 null
- 请求发出后(同步更新):loading 变为 true,其他字段不变
- 响应返回后:根据结果设置 data 或 error,loading 恢复为 false
用 await waitFor(() => expect(...).toBe(...)) 分段验证,比单次断言 final state 更可靠。
必须覆盖的四大边界场景
每个核心异步逻辑,至少配一组对应边界测试:
- 超时测试:设极短 timeout(如 30ms),断言进入 catch 分支,且未触发 success 回调
- 取消测试:创建 AbortController,手动 abort,验证 fetch 被中断、后续 .then 不执行、清理逻辑(如 clearTimeout)已运行
- 竞态测试:先发慢请求(延迟 200ms),再发快请求(延迟 50ms),断言 UI 最终只显示快请求结果,慢请求回调未被调用
- 错误传播测试:mock 接口返回 4xx/5xx 状态,检查组件是否正确显示 error message 并重置 loading
避免常见陷阱
看似小细节,却常导致测试 flaky 或掩盖问题:
- 每个
test都显式加timeout,比如jest.setTimeout(10000);Jest 默认 5s 对边界测试太宽松 - 禁用
done回调写法,统一用async/await + waitFor,错误堆栈更清晰,调试更直接 - React 组件中测异步更新,必须包在
act(async () => {...})里,否则可能报 warning 或漏渲染 - 测试完记得
jest.clearAllMocks()或重置 mock,防止测试间状态污染











