Next.js项目中使用jest.spyOn无法Mock自定义Hook的问题
动态Mock自定义Hook并修改返回值的解决方案(Next.js 12.3.0)
问题背景
在Next.js 12.3.0项目中,需要Mock自定义Hook并在单个测试用例中动态修改其返回值,当前遇到以下问题:
- 直接使用
jest.spyOn监听导出的Hook模块时,报错TypeError: Cannot redefine property: useMyHook - 使用
jest.mock虽能Mock成功,但无法在单个测试用例中动态调整返回值 - 现有
mock-hooks.js中的Mock逻辑不生效,测试仍输出原始Hook的返回值
现有代码
useMyHook.jsx
const useMyHook = () => { const [val, setVal] = useState(200) return { val } } export { useMyHook }
Hello.jsx
import { useTestHook } from "@/hooks/useTestHook"; export const Hello = () => { const { val } = useTestHook(); console.log({ val }); return ( <div> <h2>Hello</h2> </div> ); };
mock-hooks.js
import { useMyHook } from "@/hooks/useMyHook"; // import * as Hook from "@/hooks/useMyHook"; const mocks = { useMyHook: (val = 9999) => { jest .spyOn({ useMyHook }, "useMyHook") .mockImplementation(() => ({ val })); /** 此Mock方法之前可用,但现在报错 `TypeError: Cannot redefine property: useMyHook` */ // jest.spyOn(Hook, "useMyHook") // .mockImplementation(() => ({ val })); }, }; export { mocks };
Hello.test.jsx
import { mocks } from "@/utils/test/mock-hooks"; import { Hello } from "./Hello"; import { render } from "@testing-library/react"; describe("Hello test", () => { beforeEach(() => { mocks.useMyHook(12345); render(<Hello />, { wrapper: ({ children }) => <>{children}</>, }); }); test("should render the main container", () => { screen.debug() }); });
项目依赖
{ ... "dependencies": { "next": "12.3.0", "react": "17.0.2", "react-dom": "17.0.2" }, "devDependencies": { "@babel/core": "7.17.8", "@testing-library/jest-dom": "5.16.4", "@testing-library/react": "12.1.2", "@testing-library/react-hooks": "7.0.2", "babel-jest": "27.4.6", "jest": "28.1.0", "jest-environment-jsdom": "28.1.0" } }
jest.config.js
const nextJest = require("next/jest"); const createJestConfig = nextJest({}); const customJestConfig = { collectCoverageFrom: ["**/*.{js,jsx,ts,tsx}"], verbose: true, modulePaths: ["<rootDir>/"], modulePathIgnorePatterns: ["<rootDir>/.next"], testPathIgnorePatterns: ["<rootDir>/node_modules/", "<rootDir>/.next/"], transform: { "^.+\\.(js|jsx|ts|tsx)$": ["babel-jest", { presets: ["next/babel"] }], }, transformIgnorePatterns: [ "/node_modules/", "^.+\\.module\\.(css|sass|scss)$", ], testEnvironment: "jsdom", moduleNameMapper: { "^@/(.*)$": "<rootDir>/$1", }, }; module.exports = createJestConfig(customJestConfig);
.babelrc
{ "presets": ["next/babel"], "plugins": [] }
可行解决方案
方案1:jest.mock配合可动态修改的Mock函数
通过全局Mock整个Hook模块,再修改Mock函数的实现来动态调整返回值:
修改mock-hooks.js
import * as HookModule from "@/hooks/useMyHook"; // 全局Mock整个模块,默认返回原始值 jest.mock("@/hooks/useMyHook", () => ({ useMyHook: jest.fn(() => ({ val: 200 })) })); const mocks = { useMyHook: (val = 9999) => { // 动态更新Mock函数的返回值 HookModule.useMyHook.mockImplementation(() => ({ val })); }, // 重置Mock,避免用例间状态污染 resetAllMocks: () => { jest.clearAllMocks(); HookModule.useMyHook.mockImplementation(() => ({ val: 200 })); } }; export { mocks };
修改Hello.test.jsx
import { mocks } from "@/utils/test/mock-hooks"; import { Hello } from "./Hello"; import { render, screen } from "@testing-library/react"; describe("Hello test", () => { beforeEach(() => { mocks.resetAllMocks(); // 每个用例前重置Mock }); test("render with default mock value", () => { render(<Hello />); // 此时val为默认的200 }); test("render with custom value 12345", () => { mocks.useMyHook(12345); // 先设置自定义返回值,再渲染组件 render(<Hello />); // 此时val为12345 }); });
方案2:绕过模块冻结问题
Next.js 12会冻结顶层模块导出,导致直接spyOn单个Hook报错。可以通过统一导出层规避:
1. 新增统一导出文件
在hooks目录下创建index.js:
export { useMyHook } from "./useMyHook";
2. 修改Hello组件的导入路径
import { useMyHook } from "@/hooks"; // 替换原@/hooks/useMyHook
3. 修改mock-hooks.js
import * as HookModule from "@/hooks"; const mocks = { useMyHook: (val = 9999) => { jest.spyOn(HookModule, "useMyHook") .mockImplementation(() => ({ val })); }, // 恢复原始Hook实现 resetUseMyHook: () => { HookModule.useMyHook.mockRestore(); } }; export { mocks };
关键注意事项
- 必须在渲染组件前调用Mock设置方法,否则组件会使用原始Hook的返回值
- 每个测试用例结束后要重置Mock,避免用例间状态污染,可使用
mockRestore()或clearAllMocks() - Next.js 12的模块冻结机制会导致直接
spyOn单个导出Hook失败,需通过统一导出层或全局Mock规避
内容的提问来源于stack exchange,提问作者javascript-wtf
相关产品推荐
相关产品推荐

