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

React Hook Form测试时isValid始终为false问题求助

问题分析

你遇到的核心问题是:TeamUpdateModal组件在本地运行时表单校验正常,但Jest单元测试中isValid始终为false,导致提交逻辑被拦截,updateTeamAsync未被调用。主要原因有以下几点:

  1. React Hook Form用法错误:同时使用Controller和register注册字段,导致字段状态管理混乱,测试环境下表单校验状态未正确更新。
  2. 测试文件存在多处错误:
    • 导入了错误的组件(TeamRegistrationModal而非TeamUpdateModal)
    • Mock了错误的Redux异步函数(createTeamAsync而非组件中使用的updateTeamAsync)
    • useAppSelector的模拟逻辑存在问题,未正确返回selectProgress的值
    • 渲染组件时未传入team参数,导致表单默认值初始化异常
    • 未模拟useParams所需的路由环境,teamId获取异常
  3. 冗余的校验判断:在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 01:18:15