You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 13:45:02