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

如何用Jest和React Testing Library测试自定义Hook内的函数

自定义React Hook测试问题解答

自定义Hook代码(useOpenTab.tsx)

import { useRouteParams } from "hooks";

import { useOpenTabMutation } from "state/modules/tab/api";

type FilteredData = any;

export function useOpenTab() {
  const { assetId } = useRouteParams();
  const [openTabAction] = useOpenTabMutation();

  const openItems = (filteredData: FilteredData[]) => {
    const data = filteredData.reduce((acc, item) => {
      if (
        acc.find(
          (order) =>
            order.id === item.id &&
            order.tag === item.location
        )
      ) {
        return acc;
      }
      acc.push({ id: item.number, tag: item.location });
      return acc;
    }, [] as { id: string; tag: string }[]);
    
    openTabAction({ assetId, data }); // 需要确认此处的"data"值
  };
  return openItems;
}

测试代码(openTab.test.ts)

import { renderHook } from "@testing-library/react";
// eslint-disable-next-line import/no-named-as-default
import { useRouteParams } from "hooks";
import * as useCustomHook from "../useOpenTab";
import { mockedData } from "./mockedData";


const resultAfterFunction = [
  {
    id: "1",
    tag: "1",
  },
  {
    id: "2",
    tag: "2",
  },
  {
    id: "3",
    tag: "3",
  },
  {
    id: "4",
    tag: "4",
  },
];

// Mocking Selector hooks
jest.mock("hooks");
const useRouteParamsMock = useRouteParams as jest.Mock;
const openItemsMock = jest.fn();

jest
  .spyOn(useCustomHook, "useOpenTab")
  .mockImplementation(() => {
    return openItemsMock; // mocking the openItems function
  });


describe("data param of action should be correct", () => {
  beforeEach(() => {
    // mocking the useParams hook
    useRouteParamsMock.mockClear();
    useRouteParamsMock.mockReturnValueOnce({ assetId: 1 });
  });

  it("should return an array with object inside", async () => {
    const { result } = renderHook(() =>
      useCustomHook.useOpenTab()
    );
    const openItems = result.current;
    openItems(mockedData);
    expect(openItemsMock).toHaveBeenLastCalledWith({
      id: 1,
      data: resultAfterFunction,
    });

  });
});

问题与解答

1. 目前的测试代码是否正确?

不正确。你完全mock了整个useOpenTab Hook,相当于没有测试原Hook里的openItems核心逻辑——包括reduce数据处理、调用openTabAction的逻辑,只是在测试自己写的openItemsMock函数,完全失去了测试的意义。

2. 如何监听openTabAction函数,查看传入的data参数值?

需要mock useOpenTabMutation(而不是整个Hook),让它返回一个被jest监听的mock函数,然后直接测试原Hook的逻辑:

import { renderHook } from "@testing-library/react";
import { useRouteParams } from "hooks";
import { useOpenTabMutation } from "state/modules/tab/api";
import { useOpenTab } from "../useOpenTab";
import { mockedData } from "./mockedData";

// 预期的reduce处理后的数据
const expectedData = [
  { id: "1", tag: "1" },
  { id: "2", tag: "2" },
  { id: "3", tag: "3" },
  { id: "4", tag: "4" },
];

// mock依赖的hooks
jest.mock("hooks", () => ({
  useRouteParams: jest.fn(),
}));

jest.mock("state/modules/tab/api", () => ({
  useOpenTabMutation: jest.fn(),
}));

describe("useOpenTab - openItems function", () => {
  const mockOpenTabAction = jest.fn();

  beforeEach(() => {
    // 重置所有mock
    jest.clearAllMocks();
    // mock useRouteParams返回固定assetId
    (useRouteParams as jest.Mock).mockReturnValue({ assetId: 1 });
    // mock useOpenTabMutation返回mock的action函数
    (useOpenTabMutation as jest.Mock).mockReturnValue([mockOpenTabAction]);
  });

  it("should process filteredData correctly and call openTabAction with right params", () => {
    // 渲染原Hook
    const { result } = renderHook(() => useOpenTab());
    // 拿到openItems函数并调用
    result.current(mockedData);

    // 断言openTabAction被调用,且参数正确
    expect(mockOpenTabAction).toHaveBeenCalledWith({
      assetId: 1,
      data: expectedData,
    });
  });
});

3. 解决jest.spyOn的Cannot redefine property错误

这个错误是因为你试图spyOn一个直接导出的函数,jest默认无法重新定义这类属性。解决方式:

  • 放弃spyOn整个useOpenTab,转而mock它的依赖(useRouteParams和useOpenTabMutation),直接测试原Hook的真实逻辑(这也是正确的测试思路)
  • 如果你一定要spyOn,需要将导出改为let声明或配置jest允许重新定义属性,但前者更符合测试规范,同时也能解决错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 06:06:12