如何用react-testing-library和react-hook-form测试无提示表单验证逻辑?
如何测试无错误UI的react-hook-form表单验证逻辑
针对你遇到的「验证失败时组件不渲染错误提示,无法通过UI断言测试验证逻辑」的问题,以下是几个实用的测试方案,以及关于是否mock useForm的建议:
方案1:测试提交行为的结果(推荐)
遵循React Testing Library「测试用户行为而非实现细节」的理念,通过验证提交是否触发预期回调来判断验证逻辑是否生效:
组件示例
import { useForm } from 'react-hook-form'; import { yupResolver } from '@hookform/resolvers/yup'; import * as yup from 'yup'; const schema = yup.object({ email: yup.string().email('无效邮箱').required('必填'), }); export const LoginForm = ({ onSubmit }) => { const { register, handleSubmit } = useForm({ resolver: yupResolver(schema), mode: 'onSubmit', }); return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register('email')} placeholder="邮箱" /> <button type="submit">登录</button> </form> ); };
测试代码
import { render, screen, fireEvent } from '@testing-library/react'; import { LoginForm } from './LoginForm'; import { vi } from 'vitest'; test('输入无效邮箱时,提交不触发回调', async () => { const mockSubmit = vi.fn(); render(<LoginForm onSubmit={mockSubmit} />); // 输入无效值并提交 fireEvent.change(screen.getByPlaceholderText('邮箱'), { target: { value: 'invalid-email' } }); fireEvent.submit(screen.getByRole('form')); expect(mockSubmit).not.toHaveBeenCalled(); }); test('输入有效邮箱时,提交触发回调并传递正确数据', async () => { const mockSubmit = vi.fn(); render(<LoginForm onSubmit={mockSubmit} />); fireEvent.change(screen.getByPlaceholderText('邮箱'), { target: { value: 'test@example.com' } }); fireEvent.submit(screen.getByRole('form')); expect(mockSubmit).toHaveBeenCalledWith({ email: 'test@example.com' }); });
方案2:通过暴露Form实例验证内部状态
如果需要更细粒度的验证(比如检查具体字段的错误类型),可以在组件中添加一个回调props,将useForm返回的实例暴露给测试代码:
组件改造
export const LoginForm = ({ onSubmit, onFormReady }) => { const form = useForm({ resolver: yupResolver(schema), mode: 'onSubmit', }); const { register, handleSubmit } = form; // 暴露form实例给测试 useEffect(() => { onFormReady?.(form); }, [form, onFormReady]); return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register('email')} placeholder="邮箱" /> <button type="submit">登录</button> </form> ); };
测试代码
test('邮箱为空时,form状态标记验证失败', async () => { let formInstance; render(<LoginForm onFormReady={(form) => (formInstance = form)} />); fireEvent.submit(screen.getByRole('form')); expect(formInstance.formState.isValid).toBe(false); expect(formInstance.formState.errors.email).toBeDefined(); expect(formInstance.formState.errors.email.message).toBe('必填'); });
是否需要mock useForm?
不推荐。mock useForm会让测试脱离真实的验证逻辑,变成「测试mock是否正确」而非「测试表单验证是否生效」,容易出现测试通过但实际代码存在bug的情况。
只有当你的组件与react-hook-form耦合极深,且需要单独隔离测试某个非验证逻辑时,才考虑局部mock,但要确保mock的逻辑与真实useForm的行为一致。
内容的提问来源于stack exchange,提问作者SmartFellah
相关产品推荐
相关产品推荐

