React Jest单元测试:无效表单仍触发onSubmit问题排查
问题:React表单原生验证在Jest测试中不生效
测试一个接收onSubmit函数的React提交表单组件,组件包含必填的email类型输入框。在浏览器中提交无效邮箱时,浏览器会自动阻止提交并显示错误提示;但在Jest测试中执行相同操作,mock的onSubmit函数仍会被调用,导致测试失败。
组件代码
interface AppProps { onSubmit: () => Promise<any>; onComplete: (data: any) => void; } function App(props: AppProps) { function handleSubmit(event: any) { event.preventDefault(); props.onSubmit().then((response) => { props.onComplete(response); }); } return ( <form onSubmit={handleSubmit}> <label htmlFor="email"> Email </label> <input id="email" type="email" required /> <button> Submit </button> </form> ); } export default App;
测试代码
import { fireEvent, render, screen } from "@testing-library/react"; import App from "./App"; test("Invalid form isn't submitted", async () => { const onSubmit = jest.fn(); onSubmit.mockResolvedValue(""); const onComplete = jest.fn((value) => { console.log("Function called"); }); render(<App onSubmit={onSubmit} onComplete={onComplete} />); const input = screen.getByLabelText("Email"); fireEvent.change(input, { target: { value: "invalid" } }); const button = screen.getByText("Submit"); fireEvent.click(button); expect(onSubmit).not.toBeCalled(); });
测试失败输出
FAIL src/App.test.tsx ● Invalid form isn't submitted expect(jest.fn()).not.toBeCalled() Expected number of calls: 0 Received number of calls: 1
解决方案
问题根源
fireEvent.click(button)是直接触发按钮的点击事件,不会触发浏览器原生的表单验证流程,所以会直接调用form的onSubmit回调。而真实浏览器中点击提交按钮时,会先执行表单验证,验证不通过则不会触发onSubmit。
两种解决方式
- 使用
userEvent模拟真实用户交互@testing-library/user-event会模拟更贴近真实用户的操作,包括触发表单验证。需要先安装依赖:
npm install @testing-library/user-event --save-dev
修改后的测试代码:
import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import App from "./App"; test("Invalid form isn't submitted", async () => { const user = userEvent.setup(); const onSubmit = jest.fn(); onSubmit.mockResolvedValue(""); const onComplete = jest.fn(); render(<App onSubmit={onSubmit} onComplete={onComplete} />); const input = screen.getByLabelText("Email"); await user.type(input, "invalid"); const button = screen.getByText("Submit"); await user.click(button); expect(onSubmit).not.toBeCalled(); });
- 在组件中手动验证表单
在handleSubmit中主动调用表单的checkValidity()方法,验证不通过则不执行后续逻辑:
修改后的组件代码:
interface AppProps { onSubmit: () => Promise<any>; onComplete: (data: any) => void; } function App(props: AppProps) { function handleSubmit(event: React.FormEvent<HTMLFormElement>) { event.preventDefault(); const form = event.currentTarget; // 手动检查表单有效性 if (!form.checkValidity()) { // 触发浏览器的错误提示(可选) form.reportValidity(); return; } props.onSubmit().then((response) => { props.onComplete(response); }); } return ( <form onSubmit={handleSubmit}> <label htmlFor="email"> Email </label> <input id="email" type="email" required /> <button> Submit </button> </form> ); } export default App;
这种方式不管是浏览器环境还是测试环境,都会先验证表单有效性,再决定是否调用onSubmit。
内容的提问来源于stack exchange,提问作者Sergio Loza
相关产品推荐
相关产品推荐

