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

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内部,提升逻辑不会生效。

修复步骤

  1. 将jest.mock移到测试文件顶部,组件导入前执行
  2. 确保Mock路径与组件中导入路径完全一致
  3. 按需调整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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 23:26:05