vitest中react-hook-form的zodResolver验证异常问题排查
Zod + React Hook Form + Vitest 集成测试失败排查方案
问题背景
我有一个Zod验证Schema,在运行环境和单元测试中都正常,但和React Hook Form结合做Vitest集成测试时,以下两个用例始终失败:
- 输入正确时调用
onSubmit - 输入无效时不调用
onSubmit
相关代码片段
1. Zod Schema 定义
import { z } from "zod"; const UserSchema = z.object({ name: z.string().min(2, "名称至少需要2个字符"), email: z.string().email("请输入有效的邮箱地址"), age: z.number().min(18, "必须年满18岁") }); export type UserFormData = z.infer<typeof UserSchema>; export default UserSchema;
2. 测试数据
const validData = { name: "张三", email: "zhangsan@example.com", age: 25 }; const invalidData = { name: "李", email: "invalid-email", age: 17 };
3. Zod Schema 单元测试(正常通过)
import { describe, it, expect } from "vitest"; import UserSchema from "./userSchema"; describe("UserSchema 验证", () => { it("验证有效数据通过", () => { const result = UserSchema.safeParse(validData); expect(result.success).toBe(true); }); it("验证无效数据失败", () => { const result = UserSchema.safeParse(invalidData); expect(result.success).toBe(false); expect(result.error?.issues).toHaveLength(3); }); });
4. 表单组件实现
import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import UserSchema, { UserFormData } from "./userSchema"; export default function UserForm({ onSubmit }: { onSubmit: (data: UserFormData) => void }) { const { register, handleSubmit, formState: { errors } } = useForm<UserFormData>({ resolver: zodResolver(UserSchema) }); return ( <form onSubmit={handleSubmit(onSubmit)}> <div> <label>名称</label> <input {...register("name")} /> {errors.name && <span>{errors.name.message}</span>} </div> <div> <label>邮箱</label> <input {...register("email")} /> {errors.email && <span>{errors.email.message}</span>} </div> <div> <label>年龄</label> <input type="number" {...register("age", { valueAsNumber: true })} /> {errors.age && <span>{errors.age.message}</span>} </div> <button type="submit">提交</button> </form> ); }
5. 集成测试代码(失败用例)
import { describe, it, expect, vi } from "vitest"; import { render, screen, fireEvent } from "@testing-library/react"; import UserForm from "./UserForm"; describe("UserForm 集成测试", () => { it("输入正确数据时调用onSubmit", async () => { const mockSubmit = vi.fn(); render(<UserForm onSubmit={mockSubmit} />); fireEvent.change(screen.getByLabelText("名称"), { target: { value: validData.name } }); fireEvent.change(screen.getByLabelText("邮箱"), { target: { value: validData.email } }); fireEvent.change(screen.getByLabelText("年龄"), { target: { value: validData.age.toString() } }); fireEvent.submit(screen.getByText("提交")); expect(mockSubmit).toHaveBeenCalledTimes(1); // 失败:调用次数为0 expect(mockSubmit).toHaveBeenCalledWith(validData); }); it("输入无效数据时不调用onSubmit", async () => { const mockSubmit = vi.fn(); render(<UserForm onSubmit={mockSubmit} />); fireEvent.change(screen.getByLabelText("名称"), { target: { value: invalidData.name } }); fireEvent.change(screen.getByLabelText("邮箱"), { target: { value: invalidData.email } }); fireEvent.change(screen.getByLabelText("年龄"), { target: { value: invalidData.age.toString() } }); fireEvent.submit(screen.getByText("提交")); expect(mockSubmit).not.toHaveBeenCalled(); // 失败:调用了1次 }); });
排查与修复方案
1. 处理异步验证的时序问题
React Hook Form结合Zod resolver的验证是异步的,测试断言会在验证完成前执行,导致结果不符合预期。用waitFor包裹断言,等待验证流程完成:
import { waitFor } from "@testing-library/react"; // 有效数据测试用例修改 await waitFor(() => { expect(mockSubmit).toHaveBeenCalledTimes(1); }); // 无效数据测试用例修改 await waitFor(() => { expect(mockSubmit).not.toHaveBeenCalled(); });
2. 确保表单提交事件正确触发
有时候按钮文本匹配可能出问题,直接获取表单元素触发提交更可靠:
fireEvent.submit(screen.getByRole("form"));
3. 模拟用户完整输入流程
添加blur事件模拟用户离开输入框的行为,确保React Hook Form及时更新字段状态:
// 年龄输入框示例 fireEvent.change(screen.getByLabelText("年龄"), { target: { value: validData.age.toString() } }); fireEvent.blur(screen.getByLabelText("年龄"));
4. 检查版本兼容性
确保react-hook-form、@hookform/resolvers、zod三者版本兼容,建议升级到最新稳定版:
npm update react-hook-form @hookform/resolvers zod
5. 验证字段类型转换
对于valueAsNumber的字段,可在提交前断言字段值是否正确转换,确保状态同步:
await waitFor(() => { expect(screen.getByLabelText("年龄")).toHaveValue(validData.age.toString()); });
内容的提问来源于stack exchange,提问作者gomgun...
相关产品推荐
相关产品推荐

