单元测试中模拟网络重试需用 Jest mock 实现可控失败序列,配合 fake timers 验证延迟与重试次数,并覆盖成功、耗尽、首次成功、禁用重试及中断等边界场景,推荐依赖注入而非全局 mock。

在单元测试中模拟复杂网络重试,关键是让被测函数“经历”可控的失败序列,并验证它是否按预期重试指定次数、等待合理延迟、正确响应最终结果。不能依赖真实网络,而要通过可编程的 mock 替代 fetch 或 axios 等底层调用。
用 Jest mock 模拟分阶段失败行为
核心是让模拟函数前 n 次抛错、第 n+1 次成功,从而触发重试逻辑:
- 用
jest.fn()创建一个返回 Promise 的 mock 函数,内部维护调用计数器 - 首次调用时
reject(new TypeError('Network error')),第二次同样 reject,第三次才resolve({ ok: true, json: () => Promise.resolve({ data: 1 }) }) - 测试后断言
mockFn.mock.calls.length === 3,确认重试了 2 次(共发起 3 次请求) - 若重试配置为最大 3 次,则应断言调用次数为 4(首次 + 3 次重试)
配合 fake timers 验证延迟与节奏
指数退避或抖动延迟必须可测试,否则重试逻辑可能看似运行却未真正生效:
将后端 Java 接口文档转换为前端 TypeScript 类型定义(interface / type)。输入:飞书文档、Spring Boot 接口文档、Swagger/OpenAPI、手写 API 文档等。输出:包含请求参数与响应结果的 .md 文件,含完整 JSDoc 注释。触发关键词:Java 转 TS、接口转换、生成前端类型、接口文档转 TypeScript、DTO/VO/BO 转 interface、后端文档转前端类型、生成接口类型、类型定义生成。
- 在测试开始前调用
jest.useFakeTimers(),暂停真实 setTimeout - 执行被测函数后,用
jest.runAllTimers()快进所有延迟,或jest.advanceTimersByTime(1000)精确推进 - 结合 mock 调用次数和
setTimeout调用次数(expect(setTimeout).toHaveBeenCalledTimes(2))交叉验证 - 若含抖动,可固定
Math.random返回值(jest.spyOn(Math, 'random').mockReturnValue(0.3))确保测试稳定
覆盖边界与异常路径
只测“成功重试”不够,真实场景中失败耗尽、首次就成、零重试等都需验证:
立即学习“Java免费学习笔记(深入)”;
-
重试耗尽仍失败:mock 始终 reject,断言最终错误被抛出,且调用次数等于
maxRetries + 1 - 首次即成功:mock 直接 resolve,断言调用仅 1 次,无任何 setTimeout 被调用
-
禁用重试:传入
maxRetries: 0,确认不进入重试分支,也不产生延迟 -
AbortController 中断:创建 controller 并在第一次失败后立即
controller.abort(),断言后续重试未发生
避免全局污染,推荐依赖注入
直接 mock 全局 fetch 虽简单,但易引发冲突或掩盖设计问题:
- 将请求函数作为参数传入重试器(如
retry(fn, options)),测试时直接传入 mock 函数,解耦清晰 - 对 React 组件,可通过自定义 Hook 的依赖注入方式传入请求方法,测试时替换为受控 mock
- 避免
jest.mock('node-fetch')这类全局 mock,除非项目统一约定且严格隔离测试环境

















