React测试中使用react-testing-library无法Mock自定义Hook问题
React Testing Library测试时,usePermissions Hook无法被Mock返回true
问题描述
测试CreateCatalog组件时,组件内的usePermissions Hook始终返回false,尝试Mock该Hook让它返回true,但代码未生效。
测试代码
it("when click on save and form is correct calls CatalogService.updateCatalog", async () => { const emptyCatalog: API.Catalog = null; jest.mock("hooks/auth/AuthHooks", () => ({ usePermissions: mockUsePermission, })); mockUsePermission.mockReturnValue([true, true]); const onUpdateCatalog = jest.fn(); render( <CreateCatalog onUpdateCatalog={catalog => onUpdateCatalog(catalog)} {...emptyCatalog} /> ); fireEvent.change(screen.getByRole("textbox", { name: /name/i }), { target: { value: "test" } }); fireEvent.change(screen.getByRole("textbox", { name: /description/i }), { target: { value: "description" } }); const saveButton = screen.getByTestId("save-button").closest("button"); expect(saveButton.disabled).toBeFalsy(); fireEvent.click(saveButton); await waitFor(() => CatalogService.updateCatalog); expect( CatalogService.createCatalog as jest.MockedFunction< typeof CatalogService.createCatalog > ).toHaveBeenCalled(); });
测试文件顶部代码
import { CreateCatalog } from "../CreateCatalog"; import { render, fireEvent, screen, waitFor } from "test-utils"; import CatalogService from "services/catalogs"; import { createMockCatalog } from "test-utils/mocks/Catalog.mock"; const mockUsePermission = jest.fn((...permissionCodes) => { return permissionCodes.map(() => true); }); jest.spyOn(CatalogService, "createCatalog").mockImplementation(jest.fn()); jest.spyOn(CatalogService, "updateCatalog").mockReturnValue( Promise.resolve({ data: {} as API.Catalog, status: 200, statusText: "200", headers: null, config: null }) ); const onUpdateCatalog = jest.fn();
组件内代码
export const CreateCatalog: React.FC<API.Catalog & { onUpdateCatalog: (c: API.Catalog) => void; }> = ({ onUpdateCatalog, ...catalog }) => { const { name, setName, description, setDescription, status, setStatus, statusOptions } = useCatalogForm(catalog); const [canCreate, canEditPermission] = usePermissions( Catalog.create, Catalog.details_edit ); const isSaveDisabled = (!canEditPermission && !!catalog.id) || (!catalog.id && !canCreate); console.log(canCreate, canEditPermission) return ( <Create disableSave={isSaveDisabled} successMessage={ catalog ? "Catalog successfully updated!" : "Catalog successfully created!" } // ... 组件其余代码 ); };
问题现象
测试运行时,组件内的console.log(canCreate, canEditPermission)始终输出false, false。
原usePermissions Hook实现
export const usePermissions = (...permissionCodes: Permission[]): boolean[] => { return AuthService.checkPermissions(permissionCodes); };
解决方案
问题根源
jest.mock的执行时机错误:你在测试用例(it块)内部调用jest.mock,但组件已经在文件顶部完成导入,此时模块已经被加载,Mock无法覆盖原始实现。Jest会自动将jest.mock提升到文件顶部执行,但如果放在it内部,提升逻辑不会生效。
修复步骤
- 将
jest.mock移到测试文件顶部,组件导入前执行 - 确保Mock路径与组件中导入路径完全一致
- 按需调整Mock返回值
修改后的测试文件顶部代码:
import { render, fireEvent, screen, waitFor } from "test-utils"; import CatalogService from "services/catalogs"; import { createMockCatalog } from "test-utils/mocks/Catalog.mock"; // 先Mock模块,再导入组件 jest.mock("hooks/auth/AuthHooks", () => ({ usePermissions: jest.fn(() => [true, true]), })); // 导入组件(必须在jest.mock之后) import { CreateCatalog } from "../CreateCatalog"; const mockUsePermission = jest.requireMock("hooks/auth/AuthHooks").usePermissions; jest.spyOn(CatalogService, "createCatalog").mockImplementation(jest.fn()); jest.spyOn(CatalogService, "updateCatalog").mockReturnValue( Promise.resolve({ data: {} as API.Catalog, status: 200, statusText: "200", headers: null, config: null }) ); const onUpdateCatalog = jest.fn();
测试用例中可去掉内部的jest.mock调用,直接使用已Mock的函数:
it("when click on save and form is correct calls CatalogService.updateCatalog", async () => { const emptyCatalog: API.Catalog = null; // 若需要在该用例中自定义返回值,可在此设置 mockUsePermission.mockReturnValue([true, true]); const onUpdateCatalog = jest.fn(); render( <CreateCatalog onUpdateCatalog={catalog => onUpdateCatalog(catalog)} {...emptyCatalog} /> ); // ... 其余测试逻辑 });
额外注意事项
- 确认组件中
usePermissions的导入路径与jest.mock中的路径完全一致,包括大小写、相对/绝对路径形式 - 若
usePermissions是默认导出,需调整Mock写法为jest.mock("hooks/auth/AuthHooks", () => jest.fn(() => [true, true]))
内容的提问来源于stack exchange,提问作者mouchin777
相关产品推荐
相关产品推荐

