写好异步代码测试的关键是主动击穿边界、验证状态流转、暴露隐藏缺陷,需精准控制 Promise 行为、覆盖超时/取消/竞态/异常输入四大边界,并分阶段断言状态生命周期,配合 jest.setTimeout、async/await、act 和 restoreAllMocks 等基础设施优化。

写好异步代码测试,关键不是“让它跑通”,而是主动击穿边界、验证状态流转、暴露隐藏缺陷。重点在控制时机、覆盖临界、隔离依赖、断言全过程。
精准控制 Promise 行为,杜绝真实延迟
不依赖真实网络或定时器,用 mock 替代所有外部异步源:
- 立即成功:返回 Promise.resolve(data)
- 延迟成功:返回 new Promise(r => setTimeout(() => r(data), 200))
- 立即失败:返回 Promise.reject(new Error('API offline'))
- 延迟失败:用 setTimeout 触发 reject,验证错误分支是否被正确捕获
必须覆盖的四大边界场景
每个核心异步函数都应有对应测试用例,不能只测 happy path:
使用约定式提交(Conventional Commits)生成标准化的 Git 提交信息。适用于以下场景: (1) 暂存更改时需要提交信息; (2) 审查 diff 时希望获得结构化摘要; (3) 编写 PR 描述或变更日志(changelog)条目; (4) 用户明确要求“为本次更改编写提交信息”。 该功能会分析已暂存/未暂存的 diff,并生成按类型(type)划分范围的提交信息。
- 超时处理:设置显式短 timeout(如 30ms),断言是否进入 catch 或 reject 分支,检查 loading 状态是否重置
- 取消逻辑:手动调用 abortController.abort(),验证请求是否中断、后续回调未执行、事件监听器/定时器已清理
- 竞态条件:先发慢请求、后发快请求,断言最终状态只反映快请求结果,且慢请求回调未被调用(可用 mock.calls.length === 0 验证)
- 空/异常输入:传入 null、undefined、非法 URL,确认函数不崩溃,返回合理错误或默认行为
分阶段断言状态生命周期
避免只看“最终结果”,要确认 UI 或状态按预期节奏变化:
- 初始:loading: false, data: null, error: null
- 发起后同步更新:loading: true(无延迟,立即响应)
- 成功后:loading: false, data: xxx, error: null
- 失败后:loading: false, data: null, error: instanceOf Error
- 用 await waitFor(() => expect(...).toBe(...)) 分步验证,比单次断言更可靠
测试基础设施细节优化
容易忽略但影响稳定性和可读性:
- 每个 test 块显式设 jest.setTimeout(1000),避免默认 5s 掩盖慢逻辑问题
- 禁用 done 回调,统一用 async/await + waitFor,错误堆栈清晰、调试友好
- React 组件测试中,包裹 act(() => ...) 确保渲染同步完成,再用 waitFor 等待异步更新
- 全局清理:每个 test 后 restoreAllMocks(),防止 mock 泄漏干扰后续用例










