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

React Testing Library中如何Mock自定义Hook数据测试组件

依赖自定义Hook的组件Mock测试方案

问题背景

我有一个组件依赖某自定义高级Hook,目前该Hook已经完成测试且功能正常,但不清楚如何通过Mock数据的方式测试这个使用了该Hook的组件。

现有Hook测试代码

import { renderHook, act } from "@testing-library/react-hooks";
import {
  HookProvider,
  useHook
} from "hooks/useHook";
import { render } from "tests/test-utils";

export const wrapper = ({ children }) => (
  <HookProvider>
    {children}
  </HookProvider>
);

describe("Should advanced hook works", () => {
  it("Should hook has initial states", () => {
    const { result } = renderHook(() => useHook());
    expect(result.current.selectedProducts).toStrictEqual([]);
  });

  it("Test change states", async () => {
    const { result } = renderHook(() => useHook(), {
      wrapper
    });

    act(() => {
      result.current.setSelectedProducts([{_id: "a14da5", title: "product test", description: "description of product test"}]);
    });

    expect(result.current.selectedProducts[0]._id).toBe("a14da5");
  });
});

现有无法运行的组件测试代码

import { renderHook, act } from "@testing-library/react-hooks";

import {
  HookProvider,
  useHook
} from "hooks/useHook";
import { render } from "tests/test-utils";
import SelectedProductCard from ".";
import { screen, waitFor } from "@testing-library/react";

export const wrapper = ({ children }) => (
  <HookProvider>
    {children}
  </HookProvider>
);

it("Renders correctly SelectedProductCard", async () => {
  const { result } = renderHook(() => useHook(), {
    wrapper
  });

  act(() => {
    result.current.setSelectedProducts([{_id: "a14da5", title: "product test", description: "description of product test"}]);
  });

   render(
        <SelectedProductCard
          key={a14da5}
          selectedProduct={{_id: "a14da5", title: "product test", description: "description of product test"}}
        />
  );

  expect(
    screen.getAllByTestId("div-selected-product-card-container")
  ).toMatchSnapshot();
});

失败原因

当前测试无法正常运行核心有三点问题:

  • 单独通过renderHook生成的Hook实例,和后续渲染组件时消费的Hook实例是完全隔离的,在renderHook作用域内修改的状态不会同步到实际渲染的组件中
  • 代码存在基础语法错误:产品ID字符串多写了一层双引号、key={a14da5}中的a14da5既没有定义为变量,也没有作为字符串添加引号,会直接触发引用错误
  • 单个卡片渲染场景下使用getAllByTestId不符合查询逻辑,应当使用getByTestId匹配单个元素

正确实现方案

根据测试目标不同,可以选以下两种方案:

方案1:不Mock Hook,走真实Provider注入状态(适合集成测试)

该方案不需要Mock Hook的实现,直接在同一个Provider作用域内注入测试状态即可,不需要单独调用renderHook生成独立实例。

import { useEffect } from "react";
import { render, screen, act } from "tests/test-utils";
import { HookProvider, useHook } from "hooks/useHook";
import SelectedProductCard from ".";

// 测试用辅助组件,在Provider内部挂载时直接注入Mock状态
const TestStateController = ({ mockProducts }) => {
  const { setSelectedProducts } = useHook();
  useEffect(() => {
    act(() => {
      setSelectedProducts(mockProducts);
    })
  }, [mockProducts, setSelectedProducts])
  return null
}

it("Renders correctly SelectedProductCard", async () => {
  const mockProduct = {_id: "a14da5", title: "product test", description: "description of product test"};
  
  render(
    <HookProvider>
      <TestStateController mockProducts={[mockProduct]} />
      <SelectedProductCard
        key={mockProduct._id}
        selectedProduct={mockProduct}
      />
    </HookProvider>
  );

  expect(
    screen.getByTestId("div-selected-product-card-container")
  ).toMatchSnapshot();
});
  • 优点:完全贴近真实使用场景,可以覆盖组件和Hook联动的逻辑
  • 缺点:如果Hook本身逻辑复杂、依赖多,测试执行速度会更慢,需要额外处理Hook的异步逻辑、依赖项

方案2:直接Mock整个自定义Hook(适合组件单元测试)

由于Hook已经单独完成测试,测组件时可以直接Mock Hook的返回值,完全跳过Hook的真实执行逻辑,测试状态完全可控,执行速度更快。

import { render, screen } from "tests/test-utils";
import SelectedProductCard from ".";
// Mock Hook模块,保留原Provider实现避免上下文报错
jest.mock("hooks/useHook", () => ({
  ...jest.requireActual("hooks/useHook"),
  useHook: jest.fn()
}))
import { useHook } from "hooks/useHook";

// 每个用例执行后清空Mock,避免用例间状态污染
afterEach(() => {
  jest.clearAllMocks()
})

it("Renders correctly SelectedProductCard", async () => {
  const mockProduct = {_id: "a14da5", title: "product test", description: "description of product test"};
  // 直接定义Hook返回的Mock数据
  useHook.mockReturnValue({
    selectedProducts: [mockProduct],
    setSelectedProducts: jest.fn() // 组件内如果用到该方法就传入Mock函数,未使用可省略
  })
  
  render(
    <SelectedProductCard
      key={mockProduct._id}
      selectedProduct={mockProduct}
    />
  );

  expect(
    screen.getByTestId("div-selected-product-card-container")
  ).toMatchSnapshot();
});
  • 优点:测试执行速度快,不需要处理Hook本身的依赖、异步逻辑,状态完全可控
  • 缺点:属于单元测试范畴,无法覆盖组件和Hook真实联动的逻辑

内容的提问来源于stack exchange,提问作者Taís Regina Moraes Castão

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 10:27:17