React Hook Form测试时isValid始终为false问题求助
问题分析
你遇到的核心问题是:TeamUpdateModal组件在本地运行时表单校验正常,但Jest单元测试中isValid始终为false,导致提交逻辑被拦截,updateTeamAsync未被调用。主要原因有以下几点:
- React Hook Form用法错误:同时使用
Controller和register注册字段,导致字段状态管理混乱,测试环境下表单校验状态未正确更新。 - 测试文件存在多处错误:
- 导入了错误的组件(
TeamRegistrationModal而非TeamUpdateModal) - Mock了错误的Redux异步函数(
createTeamAsync而非组件中使用的updateTeamAsync) useAppSelector的模拟逻辑存在问题,未正确返回selectProgress的值- 渲染组件时未传入
team参数,导致表单默认值初始化异常 - 未模拟
useParams所需的路由环境,teamId获取异常
- 导入了错误的组件(
- 冗余的校验判断:在
handleSubmit的回调中额外判断isValid,而handleSubmit本身只会在表单校验通过时才会执行回调。
解决方案
1. 修正React Hook Form的字段注册方式
移除Controller内部的...register,改用Controller自身的rules属性定义校验规则,并通过field属性绑定表单控件:
修改后的TeamUpdateModal.tsx关键代码
// 替换原有的CompanyName Controller <Controller name="companyName" control={control} defaultValue={team?.companyName || ""} rules={{ required: "required", maxLength: 50 }} render={({ field, fieldState: { error } }) => ( <TextField variant="outlined" label="Company Name *" error={!!error} InputProps={{ inputProps: { maxLength: 50 }, }} sx={{ width: "100%" }} {...field} autoComplete="true" helperText={error?.message} /> )} /> // 替换原有的TeamName Controller <Controller name="teamName" control={control} defaultValue={team?.teamName || ""} rules={{ required: "required", maxLength: 50 }} render={({ field, fieldState: { error } }) => ( <TextField variant="outlined" label="Team Name *" error={!!error} sx={{ width: "100%" }} InputProps={{ inputProps: { maxLength: 50 }, }} {...field} autoComplete="true" helperText={error?.message} /> )} /> // 移除updateTeam中的冗余判断 const updateTeam = handleSubmit(async (data) => { if (teamId != undefined) { await dispatch( updateTeamAsync({ teamId, teamName: data["teamName"], companyName: data["companyName"], }) ); } });
2. 修正测试文件的错误
修改后的TeamUpdateModal.test.tsx
import { fireEvent, render, screen } from "@testing-library/react"; import React from "react"; import TeamUpdateModal from "./TeamUpdateModal"; // 修正组件导入 import { MemoryRouter } from "react-router-dom"; // 模拟路由环境 const mockDispatch = jest.fn(); const mockUpdateTeamAsync = jest.fn(); // 修正为组件使用的updateTeamAsync const mockHandleClose = jest.fn(); jest.mock("../../../../redux/hooks", () => ({ useAppDispatch: () => mockDispatch, useAppSelector: () => false, // 直接返回selectProgress的默认值false })); jest.mock("../../../../redux/features/teams/teamsSlice", () => ({ __esModule: true, ...jest.requireActual("../../../../redux/features/teams/teamsSlice"), updateTeamAsync: (payload) => mockUpdateTeamAsync(payload), // 修正mock函数名 selectProgress: () => false, })); describe("TeamUpdateModal", () => { beforeEach(() => { mockUpdateTeamAsync.mockClear(); mockHandleClose.mockClear(); }); test("Input is submitted successfully", async () => { // 用MemoryRouter包裹组件,模拟路由参数 render( <MemoryRouter initialEntries={["/teams/123"]}> <TeamUpdateModal open={true} handleClose={mockHandleClose} team={{ companyName: "", teamName: "" }} // 传入team参数 /> </MemoryRouter> ); const teamNameInput = screen.getByLabelText("Team Name *", { selector: "input" }) as HTMLInputElement; const companyNameInput = screen.getByLabelText("Company Name *", { selector: "input" }) as HTMLInputElement; fireEvent.change(teamNameInput, { target: { value: "username" } }); fireEvent.change(companyNameInput, { target: { value: "company" } }); const button = screen.getByText("SUBMIT"); fireEvent.click(button); expect(mockUpdateTeamAsync).toBeCalledTimes(1); expect(mockUpdateTeamAsync).toBeCalledWith({ teamId: "123", teamName: "username", companyName: "company" }); }); });
3. 额外优化点
- 使用
fireEvent.change代替fireEvent.input,更贴近真实用户输入行为 - 移除
updateTeam中的if (!isValid) return;,因为handleSubmit仅在表单校验通过时执行回调 - 用
MemoryRouter模拟路由环境,确保useParams能正确获取teamId
内容的提问来源于stack exchange,提问作者kazu
相关产品推荐
相关产品推荐

