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

Jest使用fetch和spyOn做异步测试时最后一个用例调用原生fetch问题

问题定位

核心原因是React 18 严格模式下,开发/测试环境的 useEffect 会强制执行两次,对应你代码里的 fetch 也会被调用两次,而你只通过 mockResolvedValueOnce mock 了一次 fetch 调用,刚好出现了前序测试的多余调用被后续测试的 mock 兜底、最后一个测试的多余调用无 mock 可用的情况:

  • 测试1的第2次 fetch 调用进入 pending 状态,被测试2的 mock 覆盖返回
  • 测试2的第2次 fetch 调用被测试3的 mock 覆盖返回
  • 测试3的第2次 fetch 调用被测试4的 mock 覆盖返回
  • 测试4是最后一个用例,它的第2次 fetch 调用没有后续测试的 mock 兜底,直接调用原生 fetch 访问 localhost:8080 被拒绝,抛出报错

修改为公网地址无报错的逻辑也能对应:原生 fetch 成功发出请求,测试用例拿到第一次 mock 返回的结果就断言通过结束进程,未完成的公网请求只会导致进程无法优雅退出,不会抛出连接拒绝错误。

验证方法

你可以在 mock 逻辑中加入调用次数打印,确认每个用例的 fetch 实际调用次数:

jest.spyOn(window, 'fetch').mockImplementation((...args) => {
  console.count('fetch 调用次数')
  return Promise.resolve({
    json: async () => ({ components: componentCount })
  } as Response)
})

如果每个用例打印2次调用记录,即可确认是严格模式导致的重复调用问题。

解决方案
  • 方案1:将 mockResolvedValueOnce 替换为 mockResolvedValue,允许当前测试所有 fetch 调用都返回模拟值,适配多次调用场景
jest.spyOn(window, 'fetch').mockResolvedValue({
  json: async () => ({ components: componentCount })
} as Response);
  • 方案2:明确指定 mock 生效次数,适配严格模式的两次调用
jest.spyOn(window, 'fetch').mockResolvedValueTimes(2, {
  json: async () => ({ components: componentCount })
} as Response);
  • 方案3:测试环境关闭 React 严格模式,避免 useEffect 重复执行产生的额外调用
  • 方案4:把 fetch mock 逻辑提到 beforeEach 中全局配置,避免单测内重复 mock 出现遗漏

内容的提问来源于stack exchange,提问作者sam-sjs

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 00:36:07