如何用Vitest测试React Hook Form组件?解决无法传入onSubmit问题
解决方法
要实现测试表单验证+捕获提交数据的需求,核心是把组件内部硬编码的onSubmit改成可通过props传入的外部函数,同时保留原有默认行为。这样测试时就能注入自定义处理逻辑,不影响组件原有功能。
第一步:重构组件,支持传入自定义onSubmit
修改AddUserForm组件,让它接收可选的onSubmit props,默认沿用原来的console.log逻辑:
import { ControllerProps, useForm, SubmitHandler } from "react-hook-form" import { AddUserFormType } from "../../contracts/campaigns/" import Button from "../../components/buttons/Button" import { FormInput } from "../../components/forms" // 定义组件props类型 type AddUserFormProps = { onSubmit?: SubmitHandler<AddUserFormType> } const AddUserForm = ({ onSubmit: externalOnSubmit }: AddUserFormProps) => { const { control, handleSubmit, formState: { errors }, } = useForm<AddUserFormType>() // 优先使用外部传入的onSubmit,无传入则用默认的console.log逻辑 const defaultOnSubmit: SubmitHandler<AddUserFormType> = (data) => console.log(data) const finalOnSubmit = externalOnSubmit || defaultOnSubmit const myControl = control as unknown as ControllerProps["control"] return ( <form onSubmit={handleSubmit(finalOnSubmit)}> <FormInput type="text" control={myControl} name="firstName" label="First Name" error={errors.firstName?.message} rules={{ required: { value: true, message: "First name is required" } }} /> <FormInput type="text" control={myControl} name="lastName" label="Last Name" error={errors.lastName?.message} rules={{ required: { value: true, message: "Last name is required" } }} /> <FormInput type="text" control={myControl} name="phoneNumber" label="Phone Number" error={errors.phoneNumber?.message} rules={{ required: { value: true, message: "Phone Number is required" } }} /> <FormInput type="email" control={myControl} name="emailAddress" label="Email Address" error={errors.emailAddress?.message} rules={{ required: { value: true, message: "Email is required" } }} /> <Button title="Submit" type="submit" /> </form> ) } export default AddUserForm
第二步:编写Vitest测试用例
现在可以在测试中传入自定义的onSubmit函数,验证表单验证规则,同时捕获提交数据:
import { render, screen, fireEvent } from '@testing-library/react' import AddUserForm from './AddUserForm' import type { AddUserFormType } from '../contracts/campaigns/' describe('AddUserForm', () => { // 测试必填字段未填写时的验证提示 test('shows error messages for empty required fields', async () => { render(<AddUserForm />) // 直接点击提交按钮 fireEvent.click(screen.getByRole('button', { name: /submit/i })) // 验证所有错误提示是否出现 expect(await screen.findByText('First name is required')).toBeInTheDocument() expect(screen.getByText('Last name is required')).toBeInTheDocument() expect(screen.getByText('Phone Number is required')).toBeInTheDocument() expect(screen.getByText('Email is required')).toBeInTheDocument() }) // 验证表单通过时的提交逻辑,捕获提交数据 test('triggers onSubmit with correct data when all fields are valid', async () => { // 创建mock函数用于捕获提交数据 const mockOnSubmit = vi.fn() render(<AddUserForm onSubmit={mockOnSubmit} />) // 填写所有表单字段 fireEvent.change(screen.getByLabelText(/First Name/i), { target: { value: 'John' } }) fireEvent.change(screen.getByLabelText(/Last Name/i), { target: { value: 'Doe' } }) fireEvent.change(screen.getByLabelText(/Phone Number/i), { target: { value: '1234567890' } }) fireEvent.change(screen.getByLabelText(/Email Address/i), { target: { value: 'john.doe@example.com' } }) // 点击提交按钮 fireEvent.click(screen.getByRole('button', { name: /submit/i })) // 验证mock函数被正确调用,且数据符合预期 expect(mockOnSubmit).toHaveBeenCalledTimes(1) expect(mockOnSubmit).toHaveBeenCalledWith({ firstName: 'John', lastName: 'Doe', phoneNumber: '1234567890', emailAddress: 'john.doe@example.com' } as AddUserFormType) }) })
关键说明
- 重构后的组件兼容原有逻辑:父组件不传
onSubmit时,依然会默认打印提交数据。 - 测试时用
vi.fn()创建mock函数,能轻松捕获提交的表单数据,验证是否符合预期。 - 用
@testing-library/react模拟真实用户操作(输入、点击),测试结果更贴近实际使用场景。
内容的提问来源于stack exchange,提问作者ujwal lamichhane
相关产品推荐
相关产品推荐

