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

如何测试React Hook返回的单个函数在组件中是否被调用?

如何仅Mock自定义Hook返回的单个函数并保留其他逻辑?

问题背景

你想测试组件调用自定义Hook返回的setSomeValue函数是否执行,但当前的Mock方式会丢失Hook内其他逻辑,导致其他测试失败。示例代码如下:

自定义Hook useSomeHook

import { useState } from 'react';

export const useSomeHook = () => {
  const [someValue, setSomeValue] = useState();
  const [someOtherValue, setSomeOtherValue] = useState();

  // 其他逻辑...

  return {
    someValue,
    setSomeValue,
    someOtherValue,
    setSomeOtherValue,
  }
}

使用Hook的组件 SomeComponent

export const SomeComponent = () => {
  const { setSomeValue } = useSomeHook();

  return (<button onClick={() => setSomeValue('hey')}>hello</button>);
}

失败的Mock尝试

const mockSetSomeValue = jest.fn();

jest.mock('./useSomeHook.js', () => {
  const actual = jest.requireActual('./useSomeHook.js');
  return {
    ...actual,
    // 此方式丢失了Hook内其他内容,导致其他测试失败
    useSomeHook: () => ({
      setSomeValue: mockSetSomeValue,
    })
  }
});

describe('SomeComponent', () => {
  it('should call setSomeValue when button is clicked', () => {
    render(<SomeComponent />);
    // 触发按钮点击事件
    expect(mockSetSomeValue).toHaveBeenCalledWith('hey');
  });
});

可行解决方案

方案1:调用实际Hook并覆盖单个返回值

在mock函数里调用真实的Hook,然后仅替换需要Mock的函数,这样能保留Hook内的所有原有逻辑和其他返回值:

const mockSetSomeValue = jest.fn();

jest.mock('./useSomeHook.js', () => {
  const actual = jest.requireActual('./useSomeHook.js');
  return {
    ...actual,
    useSomeHook: () => {
      // 调用真实的Hook获取完整返回对象
      const originalHookReturn = actual.useSomeHook();
      // 仅替换需要Mock的函数
      return {
        ...originalHookReturn,
        setSomeValue: mockSetSomeValue,
      };
    },
  };
});

describe('SomeComponent', () => {
  it('should call setSomeValue when button is clicked', () => {
    render(<SomeComponent />);
    fireEvent.click(screen.getByText('hello'));
    expect(mockSetSomeValue).toHaveBeenCalledWith('hey');
  });
});

方案2:使用jest.spyOn动态Mock(更灵活)

如果只需要在单个测试用例中Mock,而不是全局Mock,可以用jest.spyOn来临时替换目标函数,测试完成后恢复:

import * as useSomeHookModule from './useSomeHook';

describe('SomeComponent', () => {
  it('should call setSomeValue when button is clicked', () => {
    // 保存原始Hook
    const originalUseSomeHook = useSomeHookModule.useSomeHook;
    const mockSetSomeValue = jest.fn();

    // 用spyOn替换Hook,调用原始逻辑后覆盖setSomeValue
    jest.spyOn(useSomeHookModule, 'useSomeHook').mockImplementation(() => {
      const originalReturn = originalUseSomeHook();
      return {
        ...originalReturn,
        setSomeValue: mockSetSomeValue,
      };
    });

    render(<SomeComponent />);
    fireEvent.click(screen.getByText('hello'));
    expect(mockSetSomeValue).toHaveBeenCalledWith('hey');

    // 恢复原始Hook,避免影响其他测试
    useSomeHookModule.useSomeHook.mockRestore();
  });
});

方案3:MockuseState跟踪调用(针对状态更新函数)

因为setSomeValue是useState返回的更新函数,也可以直接MockuseState来跟踪它的调用,这种方式不需要修改Hook的Mock:

import { useState } from 'react';

jest.mock('react', () => ({
  ...jest.requireActual('react'),
  useState: jest.fn(),
}));

describe('SomeComponent', () => {
  it('should call setSomeValue when button is clicked', () => {
    const mockSetSomeValue = jest.fn();
    // 让useState返回原始状态和Mock的更新函数
    useState.mockImplementation((initialState) => {
      // 判断是否是对应someValue的useState调用,或者按顺序处理
      if (initialState === undefined) { // 匹配第一个useState的初始值
        return [undefined, mockSetSomeValue];
      }
      // 其他useState调用返回原始逻辑
      const originalUseState = jest.requireActual('react').useState;
      return originalUseState(initialState);
    });

    render(<SomeComponent />);
    fireEvent.click(screen.getByText('hello'));
    expect(mockSetSomeValue).toHaveBeenCalledWith('hey');
  });
});

注意:方案3需要注意useState的调用顺序,如果Hook内的useState顺序变化,测试会失效,适合简单场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 17:28:31