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
相关产品推荐
相关产品推荐

