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

Next.js表单验证测试问题:提交按钮点击未触发验证

Next.js表单验证测试问题排查方案

问题描述

在Next.js应用中做表单验证测试时遇到问题:表单包含多个输入框,提交无效表单时页面应显示错误提示,但测试里点击提交按钮的操作似乎没生效,无法触发验证错误提示。

测试文件代码

import Login from "@/app/components/auth/login-form/login";
import { render, screen, fireEvent } from "@testing-library/react";

jest.mock("next/navigation", () => ({
    useRouter: jest.fn(),
}));

describe('Login Form Component', () => {

    it('should render the login form component with validation errors', async () => {
        render(<Login />);

        const button = screen.getByRole('button', { name: 'Login to your account' });

        fireEvent.change(screen.getByPlaceholderText('name@company.com'), {
            target: { value: '' }
        });
        fireEvent.click(button, new MouseEvent('click'));
        expect(screen.getByTestId('email-error')).toBeInTheDocument();
    });

});

组件代码

"use client";

import { RegisterForm } from "@/app/types/types";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useForm } from "react-hook-form"

export default function Login() {
    const router = useRouter();
    const {
        register,
        handleSubmit,
        formState: { errors }
    } = useForm<RegisterForm>({
        defaultValues: { email: '', password: '' }
    });
    const handleClose = () => {
        router.back();
    }
    async function handlePostData(data: RegisterForm) {
        console.log(data);
    }
    return (
        <>
            <div className="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600 border-gray-200">
                <h3 className="text-xl font-semibold text-gray-900 dark:text-white">
                    Sign in to our platform
                </h3>
                <button onClick={handleClose} type="button" className="end-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white" data-modal-hide="authentication-modal">
                    <svg className="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
                        <path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
                    </svg>
                    <span className="sr-only">Close modal</span>
                </button>
            </div>
            <div className="p-4 md:p-5">
                <form className="space-y-4" onSubmit={handleSubmit((data) => handlePostData(data))}>
                    <div>
                        <label htmlFor="email" className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your email</label>
                        <input type="email" {...register("email", { required: 'email is required to signin' })} id="email" className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white" placeholder="name@company.com" />
                        {errors.email && <p className="text-red-500" data-testid="email-error">{errors.email?.message}</p>}
                    </div>
                    <div>
                        <label htmlFor="password" className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Your password</label>
                        <input type="password" id="password" placeholder="••••••••" {...register("password", { required: 'password is required to signin' })} className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white" />
                        {errors.password && <p className="text-red-500" data-testid="password-error">{errors.password?.message}</p>}
                    </div>
                    <button type="submit" className="w-full text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Login to your account</button>
                    <div className="text-sm font-medium text-gray-500 dark:text-gray-300">
                        Not registered? <Link href="/signup" className="text-blue-700 hover:underline dark:text-blue-500">Create an account</Link>
                    </div>
                </form>
            </div>
        </>
    )
}

解决方法

  • 直接触发表单提交事件:替换点击按钮的操作,改为获取表单元素并触发submit事件。按钮点击可能因事件冒泡或其他逻辑干扰失效,直接操作表单更可靠:

    const form = screen.getByRole('form');
    fireEvent.submit(form);
    
  • 使用waitFor等待错误提示渲染:react-hook-form的错误状态更新可能需要React完成一次重渲染,用waitFor包裹断言确保元素已加载:

    import { render, screen, fireEvent, waitFor } from "@testing-library/react";
    
    // ...测试代码
    await waitFor(() => {
      expect(screen.getByTestId('email-error')).toBeInTheDocument();
    });
    
  • 移除不必要的输入框状态设置:组件默认值已将email设为空字符串,无需再用fireEvent.change设置空值,避免干扰表单初始状态。

  • 确认react-hook-form的验证模式:在useForm中明确设置mode: "onSubmit",确保提交时触发验证:

    const { register, handleSubmit, formState: { errors } } = useForm<RegisterForm>({
      defaultValues: { email: '', password: '' },
      mode: "onSubmit" // 明确指定提交时验证
    });
    
  • 检查按钮选择器是否准确:尝试用screen.getByText('Login to your account')获取按钮,避免getByRole的name匹配规则导致找不到元素。

  • 异步处理表单提交函数:由于handlePostData是异步函数,可在测试中模拟它或确保验证逻辑不受异步操作影响,比如用jest.spyOn模拟:

    jest.spyOn(global.console, 'log').mockImplementation(() => {});
    

内容的提问来源于stack exchange,提问作者jordan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 01:04:54