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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 00:30:26