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

如何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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 16:09:21