Jest配合fetch-mock测试时fetchRetry内setTimeout引发异步超时问题
问题原因
- 核心原因是你使用了
fetchMock.postOnce注册接口模拟,该方法仅能匹配1次对应路由的请求。你的重试逻辑第一次请求命中模拟规则返回503后,后续的重试请求没有对应的模拟规则匹配,导致fetch请求始终处于pending状态,外层Promise无法得到结果,最终触发超时。 - 你的
fetchRetry函数缺少fetch异常捕获逻辑,当fetch请求抛出错误(比如未匹配到mock规则、网络错误)时,外层Promise永远不会resolve或reject,也会导致超时。 - 三次重试的累计等待时间为4500ms,接近jest默认的5000ms超时阈值,环境性能波动时也可能触发超时,但你调整超时阈值后仍无效,说明核心是前两个问题。
你去掉setTimeout后测试正常,是因为去掉延迟后重试请求会同步发起,未匹配mock的错误会立即抛出,Promise不会持续挂住,所以不会触发超时。
解决方案
1. 调整fetchMock的模拟规则
不要使用仅匹配单次请求的postOnce,改用支持多次匹配的规则,根据你的测试场景选择对应配置:
- 测试多次重试都返回5xx的场景:
// 最多匹配4次post请求(1次初始请求+3次重试),全部返回503 fetchMock.post("/my-api", { status: 503 }, { repeat: 4 });
- 测试重试到最后一次成功的场景:
// 依次返回3次503,第4次返回200 fetchMock.post("/my-api", [ { status: 503 }, { status: 503 }, { status: 503 }, { status: 200, body: { /* 你的返回数据 */ } } ]);
2. 修复fetchRetry的异常捕获逻辑
补充fetch请求的错误捕获分支,避免Promise永远挂住:
const fetchRetry = (url: string, options = {}, retries = 3, timeout = 1500) => { return new Promise((resolve, reject) => { fetch(url, options) .then((res) => { if (res.ok) return resolve(res); if (retries > 0 && res.status >= 500 && res.status < 600) { setTimeout(() => { resolve(fetchRetry(url, options, retries - 1, timeout)); }, timeout); } else { resolve(res); } }) .catch(err => { // 异常场景也支持重试,重试耗尽后抛出错误 if (retries > 0) { setTimeout(() => { resolve(fetchRetry(url, options, retries - 1, timeout)); }, timeout); } else { reject(err); } }); }); }; export default fetchRetry;
3. (可选)使用jest假定时器加快测试速度
不需要真实等待setTimeout的延迟时间,直接快进定时器触发回调:
// 测试用例开头启用假定时器 jest.useFakeTimers(); fetchMock.post("/my-api", { status: 503 }, { repeat: 4 }); const expectedActions = [ { itemRef: "sample" }, ]; store.dispatch(myAction.findRef(itemRef)) .then(() => { expect(store.getActions()).toEqual(expectedActions); }); // 快进所有定时器,触发重试逻辑 jest.runAllTimers(); // jest 27+ 版本如果使用现代假定时器,可改用异步方法 // await jest.runAllTimersAsync();
内容的提问来源于stack exchange,提问作者Holis
相关产品推荐
相关产品推荐

