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时就正常展示校验错误信息

复现代码
表单组件代码
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()); }); });
问题原因
- 异步校验未等待完成:
@hookform/resolvers/yup的校验逻辑是异步执行的,哪怕yup本身的校验规则是同步写法,触发blur后校验任务会进入微任务队列,原代码触发blur后立刻打印DOM,此时校验还未执行完成,errors状态还没更新,自然看不到错误信息。 - Enzyme未同步最新渲染结果:React 17环境下Enzyme的事件模拟不会自动等待状态更新完成后同步DOM引用,异步状态变更后需要手动触发更新才能拿到最新DOM。
- 跨用例共享wrapper实例导致状态污染:原代码在测试用例之间共享同一个mount出来的wrapper实例,第一个用例触发的校验在后台执行完成后更新了组件内部状态,但没有触发Enzyme的DOM重渲染,第二个用例触发blur事件时刚好触发了重渲染,才把已经存在的错误信息渲染出来,并不是第二次blur才触发校验。
解决方法
- 每个测试用例单独挂载组件,禁止跨用例共享wrapper实例,避免测试状态互相干扰。
- 触发blur等会触发校验的事件时,使用
act包裹事件触发逻辑,配合async/await等待异步校验的微任务执行完成。 - 事件触发、异步等待完成后,调用
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
相关产品推荐
相关产品推荐

