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

Jest resetModules()引发react-router-dom Context异常及Mock失效问题

解决React List组件测试中Link与Mock Context的冲突问题

看起来你遇到了jest模块缓存和react-router上下文不匹配的双重问题,我来帮你拆解一下原因和解决方案:

问题根源分析

你当前的测试逻辑有两个矛盾点:

  1. jest.resetModules()导致react-router实例不一致:每次测试前重置模块缓存后,第二个测试里require("./List")会重新加载react-router-dom,而测试文件顶部导入的MemoryRouter是旧的模块实例。Link和MemoryRouter属于不同的模块实例,它们的内部上下文不共享,所以会抛出You should not use <Link> outside a <Router>错误。
  2. 移除resetModules()导致mock不更新:模块缓存会让第一个测试的Service mock一直生效,第二个测试的mock被忽略,始终返回空数组。

最优解决方案:用jest.mock动态修改Context实现

这种方式不需要重置模块,而是全局mock Service后,在每个测试里动态更新它的Consumer实现,完美避开两个问题:

import React from "react"
import renderer from "react-test-renderer"
import { MemoryRouter } from "react-router"
import List from "./List"

// 先全局mock Service模块,用jest.fn()占位Consumer
jest.mock("./Service", () => ({
  Context: {
    Consumer: jest.fn(),
  },
}))

// 导入mock后的Context,方便后续修改实现
import { Context } from "./Service"

describe("<List>", () => {
  // 每次测试前重置mock的调用记录,避免测试间污染
  beforeEach(() => {
    Context.Consumer.mockClear()
  })
  
  it("renders with zero items", () => {
    // 设置Consumer返回空数组
    Context.Consumer.mockImplementation((props) => props.children([]))
    
    const tree = renderer
      .create(<MemoryRouter><List /></MemoryRouter>)
      .toJSON()
    expect(tree).toMatchSnapshot()
  })
  
  it("renders with items", () => {
    // 设置Consumer返回测试数据
    Context.Consumer.mockImplementation((props) => props.children([{ name: "d", id: "d" }]))
    
    const tree = renderer
      .create(<MemoryRouter><List /></MemoryRouter>)
      .toJSON()
    expect(tree).toMatchSnapshot()
  })
})

备选方案:动态导入所有相关模块

如果你坚持要用jest.resetModules(),可以把所有模块的导入移到每个测试内部,确保每次测试加载的模块都是同一个实例:

import React from "react"
import renderer from "react-test-renderer"

describe("<List>", () => {
  beforeEach(() => jest.resetModules())
  
  it("renders with zero items", async () => {
    jest.doMock("./Service", () => ({
      Context: {
        Consumer: (props) => props.children([]),
      },
    }))
    
    // 动态导入MemoryRouter和List,保证和当前测试的模块实例一致
    const { MemoryRouter } = await import("react-router")
    const List = (await import("./List")).default
    
    const tree = renderer
      .create(<MemoryRouter><List /></MemoryRouter>)
      .toJSON()
    expect(tree).toMatchSnapshot()
  })
  
  it("renders with items", async () => {
    jest.doMock("./Service", () => ({
      Context: {
        Consumer: (props) => props.children([{ name: "d", id: "d" }]),
      },
    }))
    
    const { MemoryRouter } = await import("react-router")
    const List = (await import("./List")).default
    
    const tree = renderer
      .create(<MemoryRouter><List /></MemoryRouter>)
      .toJSON()
    expect(tree).toMatchSnapshot()
  })
})

为什么第一种方案更好?

  • 代码更简洁,不需要异步导入,可读性更强
  • 避免了模块实例不一致的风险,所有模块共享同一个上下文
  • 可以灵活添加更多断言,比如检查Context.Consumer的调用次数

内容的提问来源于stack exchange,提问作者pietro909

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:52:14