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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 02:48:21