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

使用Jest spyOn时触发TypeError: Cannot redefine property: useNavigate错误

解决Jest监听React Router V6 useNavigate时的"Cannot redefine property"错误

问题原因

React Router V6的useNavigate是ES模块导出的只读属性,jest.spyOn默认无法重定义这类属性,因此触发报错。

解决方案

方法一:用jest.mock直接模拟useNavigate(推荐)

直接替换模块的useNavigate为mock函数,避免修改只读属性的问题:

import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import React from "react";
import { MemoryRouter, useNavigate } from "react-router-dom";
import GoBack from "../Goback";

// 模拟react-router-dom,保留其他真实导出,仅替换useNavigate
jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom'),
  useNavigate: jest.fn(),
}));

describe("GoBack page", () => {
  const pageInfo = "This is error page!";
  let navigate;

  beforeEach(() => {
    // 每次测试前重置mock函数
    navigate = jest.fn();
    useNavigate.mockReturnValue(navigate);
  });

  it("renders the GoBack page and triggers navigate on button click", async () => {
    render(
      <MemoryRouter>
        <GoBack />
      </MemoryRouter>
    );

    expect(screen.getByText(pageInfo)).toBeInTheDocument();

    const goBackBtn = screen.getByRole("button");
    userEvent.click(goBackBtn);
    
    await waitFor(() => {
      expect(navigate).toHaveBeenCalled();
      // 如果GoBack组件里用的是navigate(-1),可以更精确断言:
      // expect(navigate).toHaveBeenCalledWith(-1);
    });
  });
});

方法二:临时修改属性为可写(不推荐,hack方式)

如果坚持用jest.spyOn,可以先把useNavigate设为可写,再进行监听:

import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import React from "react";
import { MemoryRouter } from "react-router-dom";
import GoBack from "../Goback";

describe("GoBack page", () => {
  const pageInfo = "This is error page!";
  let mockUseNavigate;
  let navigate;

  beforeEach(() => {
    const rrModule = require("react-router-dom");
    // 临时将useNavigate设为可写
    Object.defineProperty(rrModule, "useNavigate", { writable: true });
    // 创建spy并返回mock的navigate函数
    mockUseNavigate = jest.spyOn(rrModule, "useNavigate").mockReturnValue(jest.fn());
    navigate = mockUseNavigate();
  });

  afterEach(() => {
    // 测试后恢复属性只读状态,保持环境干净
    const rrModule = require("react-router-dom");
    Object.defineProperty(rrModule, "useNavigate", { writable: false });
    jest.restoreAllMocks();
  });

  it("renders the GoBack page and triggers navigate on button click", async () => {
    render(
      <MemoryRouter>
        <GoBack />
      </MemoryRouter>
    );

    expect(screen.getByText(pageInfo)).toBeInTheDocument();

    const goBackBtn = screen.getByRole("button");
    userEvent.click(goBackBtn);
    
    await waitFor(() => {
      expect(navigate).toHaveBeenCalled();
    });
  });
});

额外注意点

  • 原代码中const navigate = mockUseNavigate()放在describe外会导致mock提前执行,建议放在beforeEach或测试用例内部,确保每次测试的mock独立。
  • 使用waitFor时,用toHaveBeenCalled()替代toBeCalled(),断言更清晰规范。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 11:10:36