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

Jest测试react-hook-form首次onBlur触发errors不显示问题

react-hook-form onBlur模式下Jest测试首次blur不展示校验错误的问题

问题现象

使用react-hook-form结合yup实现表单校验,设置校验触发模式为onBlur,在使用Jest+Enzyme编写单元测试时出现异常表现:

  • 第一个测试用例挂载组件后,依次触发目标输入框的focus、blur事件,预期必填校验的错误信息应当渲染,但实际DOM中未出现错误信息
  • 第二个测试用例中再次触发该输入框的blur事件后,错误信息才正常展示
  • 打印两个测试用例执行后的DOM结构可稳定复现该异常,目标是实现第一个测试用例首次触发blur时就正常展示校验错误信息

测试执行后DOM结构截图

复现代码

表单组件代码

import React from 'react';
import { useForm } from 'react-hook-form';
import { string, object } from 'yup';
import { yupResolver } from '@hookform/resolvers/yup';

interface TestingProps {
  fullname: string;
}

const TestingPage: React.FC = () => {
  const schema = object().shape({
    fullname: string().required('fullname is required!')
  });

  const {
    register,
    handleSubmit,
    formState: { errors }
  } = useForm<TestingProps>({
    resolver: yupResolver(schema),
    mode: 'onBlur'
  });

  const submit = () => {};

  return (
    <div>
      <form onSubmit={handleSubmit(submit)}>
        <input className="testing" {...register('fullname')} />
      </form>
      {errors?.fullname && <p>{errors.fullname.message}</p>}
      <input type="submit" />
    </div>
  );
};
export default TestingPage;

原单元测试代码

import Enzyme, { mount } from 'enzyme';
import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
import TestingPage from './TermsAndConditions';

Enzyme.configure({ adapter: new Adapter() });

describe('Testing page test', () => {
  let wrapper: any;

  test('first attempt to show error message onblur', () => {
    wrapper = mount(<TestingPage />);
    wrapper.find('.testing').simulate('focus');
    wrapper.find('.testing').simulate('blur');
    console.log(wrapper.debug());
  });

  test('second attempt to show error message onblur', () => {
    wrapper.find('.testing').simulate('blur');
    console.log(wrapper.debug());
  });
});

问题原因

  1. 异步校验未等待完成:@hookform/resolvers/yup的校验逻辑是异步执行的,哪怕yup本身的校验规则是同步写法,触发blur后校验任务会进入微任务队列,原代码触发blur后立刻打印DOM,此时校验还未执行完成,errors状态还没更新,自然看不到错误信息。
  2. Enzyme未同步最新渲染结果:React 17环境下Enzyme的事件模拟不会自动等待状态更新完成后同步DOM引用,异步状态变更后需要手动触发更新才能拿到最新DOM。
  3. 跨用例共享wrapper实例导致状态污染:原代码在测试用例之间共享同一个mount出来的wrapper实例,第一个用例触发的校验在后台执行完成后更新了组件内部状态,但没有触发Enzyme的DOM重渲染,第二个用例触发blur事件时刚好触发了重渲染,才把已经存在的错误信息渲染出来,并不是第二次blur才触发校验。

解决方法

  1. 每个测试用例单独挂载组件,禁止跨用例共享wrapper实例,避免测试状态互相干扰。
  2. 触发blur等会触发校验的事件时,使用act包裹事件触发逻辑,配合async/await等待异步校验的微任务执行完成。
  3. 事件触发、异步等待完成后,调用wrapper.update()强制Enzyme同步最新的DOM渲染结果,再做断言或DOM打印。

修正后的测试代码如下:

import Enzyme, { mount } from 'enzyme';
import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
import { act } from 'react-dom/test-utils';
import TestingPage from './TermsAndConditions';

Enzyme.configure({ adapter: new Adapter() });

describe('Testing page test', () => {
  test('first attempt to show error message onblur', async () => {
    // 每个用例单独挂载组件
    const wrapper = mount(<TestingPage />);
    wrapper.find('.testing').simulate('focus');
    
    // 等待异步校验执行完成
    await act(async () => {
      wrapper.find('.testing').simulate('blur');
    });
    // 同步最新DOM
    wrapper.update();
    
    console.log(wrapper.debug());
    // 此时可正常断言错误信息存在
    expect(wrapper.find('p').text()).toBe('fullname is required!');
  });
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 21:54:33