如何spy测试react-router-dom的useNavigate方法是否被调用
问题说明
给react-router-dom包的useNavigate方法添加Jest spy监听,测试导航触发逻辑时,运行测试抛出报错:
TypeError: Cannot redefine property: useNavigate
点击事件可正常触发(控制台能打印绑定的called日志),但无法验证navigate方法是否被正确调用。
关联代码
业务代码 app.js
const navigate = useNavigate(); // 其余业务逻辑 return ( <a href="" data-testid={recipe.id} onClick={() => { console.log("called"); navigate(`/recipes/edit/${recipe.id}`, {state: {recipe: recipe}}); }}> Edit </a> )
测试代码 app.test.js
import * as ReactRouterDom from "react-router-dom"; const useNavigateSpy = jest.spyOn(ReactRouterDom, "useNavigate"); it("clicking on edit takes to edit view", async() => { useNavigateSpy.mockImplementation(() => jest.fn()); await act(async () => render(<BrowserRouter> <List /> </BrowserRouter>)); const user = userEvent.setup(); await user.click(screen.getByTestId("R1")); expect(useNavigateSpy).toHaveBeenCalled(); });
报错原因
- ES模块导出的属性默认不可配置,直接通过
jest.spyOn重定义导入对象的useNavigate属性会触发类型错误 - 原有测试逻辑仅监听了
useNavigate本身的调用,没有捕获useNavigate返回的navigate函数的执行,就算spy挂载成功也无法验证点击时的导航调用 - 业务代码中a标签设置了空
href="",点击时会触发默认页面跳转刷新,破坏单页应用测试环境
解决方案
方案1:mock整个react-router-dom模块(推荐,稳定性最高)
在测试文件顶部直接mockreact-router-dom模块,保留其余导出的真实实现,单独持有mock的navigate函数引用用于断言:
import { BrowserRouter } from "react-router-dom"; // 提前声明mock导航函数,用于后续断言 const mockNavigate = jest.fn(); // 重写react-router-dom模块导出 jest.mock("react-router-dom", () => ({ // 保留原模块所有真实导出的方法、组件 ...jest.requireActual("react-router-dom"), // 覆盖useNavigate实现,返回可控的mock函数 useNavigate: () => mockNavigate })); // 每个测试用例执行前清空mock调用记录,避免用例间互相影响 beforeEach(() => { mockNavigate.mockClear(); }); it("clicking on edit takes to edit view", async() => { await act(async () => render(<BrowserRouter> <List /> </BrowserRouter>)); const user = userEvent.setup(); await user.click(screen.getByTestId("R1")); // 直接断言导航方法被调用,且传入参数符合预期 expect(mockNavigate).toHaveBeenCalledWith("/recipes/edit/R1", { state: { recipe: expect.objectContaining({ id: "R1" }) } }); });
方案2:配置spy允许重定义属性(不推荐)
如果不想全量mock模块,可以在spyOn时传入配置项允许重定义属性,同时单独捕获返回的navigate函数做断言:
const mockNavigate = jest.fn(); const useNavigateSpy = jest.spyOn(ReactRouterDom, "useNavigate", { configurable: true }); useNavigateSpy.mockReturnValue(mockNavigate);
该方案依赖Jest对ES模块的兼容配置,不同版本Jest、不同构建工具下表现不一致,容易出现偶发失败。
额外优化:业务代码中的a标签建议替换为
react-router-dom提供的Link组件,或者在onClick事件中调用e.preventDefault()阻止默认跳转行为,避免点击时触发页面刷新。
内容的提问来源于stack exchange,提问作者Neha Gupta
相关产品推荐
相关产品推荐

