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

如何使用react-testing-library和Jest正确Mock useRef Hook?

React+TypeScript中Mock useRef测试useImperativeHandle暴露方法失败的解决方法

问题场景

在React+TypeScript项目中,使用react-testing-library和Jest做组件单元测试时遇到以下问题:

  • 主组件UseRefTest通过useRef关联了子组件ReferencedComponent
  • 子组件通过useImperativeHandle向外暴露了方法
  • 测试用例中点击按钮触发主组件调用子组件暴露的方法,但断言始终不通过

问题原因

直接MockuseRef无法正确关联useImperativeHandle暴露的方法——useImperativeHandle会自定义ref.current的内容,常规的useRef Mock没有处理这种自定义逻辑,导致测试中无法捕获到方法的调用行为。

解决方案

方案1:模拟子组件并自定义ref返回值

通过jest.mock模拟子组件,让它返回带有自定义ref实例的组件,直接断言方法调用:

import { render, screen, fireEvent } from '@testing-library/react';
import UseRefTest from '../UseRefTest';
import ReferencedComponent from '../ReferencedComponent';
import React from 'react';

// 模拟子组件,自定义ref返回的方法
jest.mock('../ReferencedComponent', () => {
  const mockExposedMethod = jest.fn();
  return {
    __esModule: true,
    default: React.forwardRef((props, ref) => {
      React.useImperativeHandle(ref, () => ({
        exposedMethod: mockExposedMethod
      }));
      return <div />;
    })
  };
});

test('点击按钮触发子组件方法调用', () => {
  render(<UseRefTest />);
  // 触发按钮点击
  fireEvent.click(screen.getByText('调用子组件方法'));
  // 获取模拟的方法并断言调用次数
  const { default: MockedComponent } = require('../ReferencedComponent');
  const mockMethod = MockedComponent._currentValue.exposedMethod;
  expect(mockMethod).toHaveBeenCalledTimes(1);
});

方案2:用spyOn监听useImperativeHandle

不Mock子组件,而是通过jest.spyOn监听useImperativeHandle,替换暴露的方法为mock函数:

import { render, screen, fireEvent } from '@testing-library/react';
import UseRefTest from '../UseRefTest';
import * as React from 'react';

test('点击按钮触发子组件方法调用', () => {
  const mockExposedMethod = jest.fn();
  // 监听useImperativeHandle并替换暴露的方法
  const spy = jest.spyOn(React, 'useImperativeHandle').mockImplementation((ref, createHandle) => {
    const originalHandle = createHandle();
    React.useImperativeHandle.mockRestore();
    return React.useImperativeHandle(ref, () => ({
      ...originalHandle,
      exposedMethod: mockExposedMethod
    }));
  });

  render(<UseRefTest />);
  fireEvent.click(screen.getByText('调用子组件方法'));

  expect(mockExposedMethod).toHaveBeenCalledTimes(1);
  spy.mockRestore();
});

方案3:直接传递测试用ref

如果主组件支持接收ref并传递给子组件,可在测试中创建自定义ref,直接断言方法调用:

import { render, screen, fireEvent } from '@testing-library/react';
import UseRefTest from '../UseRefTest';
import React from 'react';

test('点击按钮触发子组件方法调用', () => {
  const testRef = React.useRef<{ exposedMethod: () => void }>(null);
  // 假设主组件支持接收ref并传递给子组件
  render(<UseRefTest childRef={testRef} />);
  
  // 为ref的current赋值mock方法
  testRef.current = { exposedMethod: jest.fn() };
  
  fireEvent.click(screen.getByText('调用子组件方法'));
  expect(testRef.current?.exposedMethod).toHaveBeenCalledTimes(1);
});

关键注意点

  • 避免直接MockuseRef,因为useImperativeHandle会修改ref.current,常规Mock无法捕获这种修改
  • 测试优先贴近真实组件行为,优先选择模拟子组件而非底层Hook
  • 测试完成后记得恢复Mock方法,避免影响其他测试用例

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 08:32:17