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

使用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函数未被调用。

正确的实现方式

有两种可行方案:

  1. 明确触发表单提交
    直接获取表单元素,用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();
    });
    
  2. 给表单添加提交按钮(推荐)
    从用户体验和测试稳定性考虑,给表单添加提交按钮,此时浏览器会默认支持回车触发表单提交:
    组件修改:

    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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 10:48:27