Jest测试使用react-hook-form共享ref的React组件时报ref非函数错误
问题场景
React组件开发中使用react-hook-form做表单管理,需要将register返回的ref与第三方HookuseThirdpartyHook返回的ref共享绑定到input元素,组件实现代码如下:
const Test = () => { const { register } = useFormContext(); const mRef = useThirdpartyHook(); // Third party hook returns a ref const { ref, ...rest } = register('test'); return ( <input type="text" name="test" {...rest} ref={(e) => { ref(e); mRef.current = e; }} /> ); }; export default Test;
编写对应Jest单元测试用例时,使用@testing-library/react的render方法渲染组件,将mock的register方法传入FormProvider作为测试包裹层,测试代码如下:
import React from 'react'; import { render } from '@testing-library/react'; import { FormProvider } from 'react-hook-form'; import Test from './Test'; describe('<Test> component', () => { it('renders default correctly', () => { const wrapper = render( <FormProvider {...{ register: () => jest.fn() }}> <Test /> </FormProvider> ); expect(wrapper.baseElement).toMatchSnapshot(); }); });
报错信息
运行测试用例时抛出TypeError,提示ref不是一个函数,错误指向合并ref的回调函数位置:
<Test> component › renders default correctly TypeError: ref is not a function ref={(e) => { ref(e); ^ mRef.current = e; }}
即使尝试将ref mock为函数,问题仍然存在。
根因分析
问题出在register方法的mock结构不符合真实实现:
真实的register(name)调用后会返回一个对象,包含name、onChange、onBlur、ref四个属性,其中ref是用于绑定DOM节点的回调函数。
现有mock写法register: () => jest.fn()会让register('test')直接返回一个jest mock函数,当代码执行const { ref, ...rest } = register('test')时,是从返回的函数对象上解构ref属性——函数本身没有内置ref属性,因此拿到的ref值为undefined,自然无法作为函数调用,触发类型错误。
之前尝试mock ref不生效,本质是没有对齐register的返回结构,解构拿到的始终不是预期的ref函数。
解决步骤
- 修正register的mock结构,对齐真实API的返回格式,确保解构出的
ref是可调用的函数:
describe('<Test> component', () => { it('renders default correctly', () => { // 模拟register的真实返回结构 const mockRegister = jest.fn((fieldName) => ({ name: fieldName, onChange: jest.fn(), onBlur: jest.fn(), ref: jest.fn(), // 对应解构拿到的ref回调 })); const wrapper = render( <FormProvider register={mockRegister}> <Test /> </FormProvider> ); expect(wrapper.baseElement).toMatchSnapshot(); }); });
- 给合并ref的逻辑增加类型容错,兼容react-hook-form可能返回对象格式ref、或者ref为空的边界场景,提升代码鲁棒性:
<input type="text" name="test" {...rest} ref={(e) => { // 兼容回调ref和对象ref两种格式 if (typeof ref === 'function') { ref(e); } else if (ref) { ref.current = e; } mRef.current = e; }} />
内容的提问来源于stack exchange,提问作者user3362908
相关产品推荐
相关产品推荐

