Jest resetModules()引发react-router-dom Context异常及Mock失效问题
解决React List组件测试中Link与Mock Context的冲突问题
看起来你遇到了jest模块缓存和react-router上下文不匹配的双重问题,我来帮你拆解一下原因和解决方案:
问题根源分析
你当前的测试逻辑有两个矛盾点:
jest.resetModules()导致react-router实例不一致:每次测试前重置模块缓存后,第二个测试里require("./List")会重新加载react-router-dom,而测试文件顶部导入的MemoryRouter是旧的模块实例。Link和MemoryRouter属于不同的模块实例,它们的内部上下文不共享,所以会抛出You should not use <Link> outside a <Router>错误。- 移除
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
相关产品推荐
相关产品推荐

