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
相关产品推荐
相关产品推荐

