使用Jest spyOn时触发TypeError: Cannot redefine property: useNavigate错误
问题原因
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
相关产品推荐
相关产品推荐

