使用Jest测试React表单提交失败问题排查
React测试中mock函数未被调用的问题解决
错误信息
Expected number of calls: >= 1
Received number of calls: 0
该错误出现于使用.toHaveBeenCalled()断言mock函数执行情况时。
测试代码
import {CreateTodo} from "./CreateTodo"; import {screen, render, fireEvent} from "@testing-library/react"; import userEvent from "@testing-library/user-event"; test('adding item test', async () => { const mock = jest.fn(); render(<CreateTodo onAdd={mock}/>); const input = screen.getByRole('textbox'); userEvent.type(input, 'hello{enter}'); expect(mock).toHaveBeenCalled(); });
注:在userEvent.type后手动添加fireEvent.submit(input),测试即可正常通过。
组件代码
import {useState} from "react"; export const CreateTodo = ({onAdd}) => { const [inputs, setInputs] = useState({ text: '', completed: false, id: new Date().valueOf() }); const {text, completed} = inputs; const handleSubmit = (e) => { e.preventDefault(); if (!text) return; onAdd(inputs); } const handleChange = ({target: {name, value, checked}}) => { setInputs(prev => { return { ...prev, [name]: name === "completed" ? checked : value, } }); } return <form onSubmit={handleSubmit}> <input type="checkbox" checked={completed} name="completed" onChange={handleChange}/> <input type="text" name="text" value={text} onChange={handleChange}/> </form> }
背景说明
onAdd是父组件定义的函数,通过props传递给CreateTodo- 表单提交时,通过状态提升将新todo添加到父组件的Todos数组,由父组件修改数组并重新渲染
- 疑问:是否因
onAdd在父组件调用而非CreateTodo内部导致测试失败?
解决方案与原因分析
为什么{enter}未触发表单提交?
React Testing Library的userEvent.type模拟真实用户输入,但浏览器默认只在表单存在提交按钮时,才会监听输入框的回车事件来触发表单提交。你的表单仅包含文本输入框和复选框,没有提交按钮,因此{enter}无法触发onSubmit回调,导致mock函数未被调用。
正确的实现方式
有两种可行方案:
明确触发表单提交
直接获取表单元素,用userEvent.submit模拟提交操作,更贴近真实测试场景:test('adding item test', async () => { const mock = jest.fn(); render(<CreateTodo onAdd={mock}/>); const input = screen.getByRole('textbox'); const form = screen.getByRole('form'); await userEvent.type(input, 'hello'); await userEvent.submit(form); expect(mock).toHaveBeenCalled(); });给表单添加提交按钮(推荐)
从用户体验和测试稳定性考虑,给表单添加提交按钮,此时浏览器会默认支持回车触发表单提交:
组件修改:return <form onSubmit={handleSubmit}> <input type="checkbox" checked={completed} name="completed" onChange={handleChange}/> <input type="text" name="text" value={text} onChange={handleChange}/> <button type="submit">添加Todo</button> </form>此时原测试代码中的
userEvent.type(input, 'hello{enter}')即可正常触发表单提交,mock函数会被调用。
关于props传递的疑问
你的props传递逻辑完全正确:onAdd通过props传入CreateTodo后,实际是在组件内部的handleSubmit中被调用,和父组件是否执行该函数无关——父组件仅负责定义函数,执行时机完全由CreateTodo的表单提交事件决定。
内容的提问来源于stack exchange,提问作者asdasd
相关产品推荐
相关产品推荐

