写好异步单元测试的关键是控制执行流程而非被动等待,需明确测试目标、统一用 async/await、精准 mock 副作用、覆盖错误路径并清理 mock 状态。

写好异步单元测试,关键不是“等它跑完”,而是控制它怎么跑、什么时候跑、出错时怎么表现。异步函数本身不难测,难的是让测试稳定、可重复、不依赖外部环境。
明确测试目标:分清“行为”和“实现”
不要测试 setTimeout 或 fetch 本身是否工作——那是浏览器或 Node.js 的事。你要验证的是:
• 函数在收到成功响应后,是否调用正确的处理逻辑
• 遇到网络错误或数据异常时,是否触发预期的错误分支
• 并发调用下,状态更新是否互不干扰(比如多个请求共用一个 loading 标志)
统一等待方式:优先用 async/await,慎用 done 回调
Mocha、Jest、Vitest 都支持 async 测试函数,这是最直观的方式:
- 测试函数标记为 async,内部用 await 等待被测异步调用
- 框架自动处理 Promise 拒绝,无需手动 try/catch 或 done(err)
- 避免混用:不要在 async 测试里再传 done 参数,否则会因双重控制导致超时或跳过断言
精准模拟副作用:只 mock 真正需要隔离的部分
API 请求、数据库查询、定时器、文件读写这些都属于副作用,必须替换:
- HTTP 请求:mock global.fetch 或 axios 实例方法,返回预设的 resolve/reject Promise
- 数据库操作:用 jest.mock(‘./db’) 替换整个模块,导出可控的 query 函数
- 定时器:用 jest.useFakeTimers() 控制 setTimeout/setInterval 执行节奏,配合 jest.runAllTimers() 立即触发
- 避免过度 mock:比如一个工具函数只是做字符串拼接,就不该被 mock
覆盖错误路径:主动触发失败,不止看“成功走通”
健壮的异步逻辑一定有容错设计,测试也要跟上:
- 对 Promise 拒绝场景,用 expect(...).rejects.toThrow() 或 await expect(...).rejects.toEqual()
- 若被测函数内部有 .catch() 处理,需确保它返回的值(如默认空数组)符合预期
- 模拟网络中断、超时、500 响应、JSON 解析失败等不同错误类型,验证分支逻辑是否完整
不复杂但容易忽略:每次测试前清理 mock 状态(如 jest.clearAllMocks()),防止上一个测试的模拟行为影响下一个。

















