如何使用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); });
关键注意点
- 避免直接Mock
useRef,因为useImperativeHandle会修改ref.current,常规Mock无法捕获这种修改 - 测试优先贴近真实组件行为,优先选择模拟子组件而非底层Hook
- 测试完成后记得恢复Mock方法,避免影响其他测试用例
内容的提问来源于stack exchange,提问作者Isidro91
相关产品推荐
相关产品推荐

