为什么jest.fn()可正常mock react-router的useHistory却无法mock useLocation?
问题根因
- 第一种mock写法失效的核心是jest模块mock的作用域与执行时序问题:
- jest.mock会被提升到文件最顶部执行,但工厂函数处于隔离作用域,当项目存在多份react-router-dom实例、或者babel导入转换存在时序差异时,你在工厂函数内定义的useLocation mock实现没有被组件实际调用的useLocation命中,导致useLocation返回undefined,读取pathname时报错。
- 你用的第二种写法中,显式从mock后的react-router-dom中引入了useLocation的mock函数引用,在测试用例执行时(所有模块导入流程已完成)手动修改实现,修改会直接作用于组件调用的useLocation实例,所以不会报错。
注意:你第二种写法里的
useLocation.mockImplementation(() => { pathname: 'a' })存在语法隐患,箭头函数的大括号会被识别为函数体而非返回对象,没有return语句的情况下实际返回值为undefined,你实际运行时没有报错大概率是手误漏写了包裹对象的括号,正确写法是useLocation.mockImplementation(() => ({ pathname: 'a' }))。
解决建议
- 方法1:沿用你第二种写法的逻辑,统一导出需要mock的hook后在测试用例内重置实现,记得修复箭头函数的隐式返回对象问题:
jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useHistory: jest.fn().mockImplementation(() => ({ push: jest.fn() })), useLocation: jest.fn(), })); const { useLocation, useHistory } = require('react-router-dom'); it('should do xxx', () => { useLocation.mockImplementation(() => ({ pathname: 'a' })); const mockPush = jest.fn(); useHistory.mockImplementation(() => ({ push: mockPush })); render(<MyComponent/>); // 后续可以直接断言mockPush的调用情况 });
- 方法2(更推荐):不用手动mock hook,直接用react-router-dom提供的
MemoryRouter包裹测试组件,完全贴近组件真实运行场景,不需要处理mock时序问题:
import { render } from "@testing-library/react"; import { MemoryRouter } from 'react-router-dom'; import { MyComponent } from './你的组件路径'; it('should do xxx', () => { render( <MemoryRouter initialEntries={['/a']}> <MyComponent/> </MemoryRouter> ); // 直接写测试断言即可 });
内容的提问来源于stack exchange,提问作者Hiroki
相关产品推荐
相关产品推荐

