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

React Testing Library测试失败:表单提交未触发mock函数

React Testing Library测试失败排查:表单提交未触发onUserAdd

问题描述

我正在学习React Testing Library,开发了一个可添加姓名与邮箱至列表的简单应用,业务代码逻辑确认正常,但测试用例中验证表单提交触发onUserAdd的用例始终失败,请求排查原因。

组件代码

Home组件

"use client"
import UserForm from './components/UserForm'
import { useState } from 'react'
import UserList from './components/UserList'

export interface User{
  name: string,
  email: string
}

export default function Home() {

  const [users, setUsers] = useState<User[]>([])

  const onUserAdd = (user:User) => {
    setUsers([...users, user])
  }

  return (
    <>
      <h1>RTL App</h1>
      <UserForm onUserAdd={onUserAdd}/>
      <hr />
      <UserList users={users}/>
    </>
  )
}

UserForm组件

"use client"
import React, { useState } from 'react'


const UserForm = ({ onUserAdd }: any) => {

    const [email, setEmail] = useState('')
    const [name, setName] = useState('')

    const handleSubmit = (e: React.FormEvent) => {
        e.preventDefault()
        onUserAdd({ name, email })
    }

    return (
        <form onSubmit={handleSubmit}>
            <div>
                <label htmlFor="name">Name</label>
                <input id="name" type="text" value={name} onChange={(e) => setName(e.target.value)} />
            </div>
            <div>
                <label htmlFor="email">Email</label>
                <input id="email" type="text" value={email} onChange={(e) => setEmail(e.target.value)} />
            </div>
            <button>Add User</button>
        </form>
    )
}

export default UserForm

UserList组件

"use client"
import React from 'react'

const UserList = ({users}:any) => {

    const renderUsers = users.map((user:any) => {
        return(
            <tr key={user.name}>
                <td>{user.name}</td>
                <td>{user.email}</td>
            </tr>
        )
    })

  return (
    <table>
        <thead>
            <tr>
                <th>Name</th>
                <th>Email</th>
            </tr>
        </thead>
        <tbody>
            {renderUsers}
        </tbody>
    </table>
  )
}

export default UserList

测试用例代码

import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import UserForm from './UserForm'

test('it show two inputs and a button', () => {
    render(<UserForm />);
    const inputs = screen.getAllByRole('textbox');
    const button = screen.getByRole('button');

    expect(inputs).toHaveLength(2);
    expect(button).toBeInTheDocument();
});

test('it calls onUserAdd when form submitted', () => {
    const mock = jest.fn();

    render(<UserForm onUserAdd={mock} />);
    const nameInput = screen.getByRole('textbox', { name: /name/i });
    const emailInput = screen.getByRole('textbox', { name: /email/i });
    userEvent.type(nameInput, 'jane');
    userEvent.type(emailInput, 'jane@jane.com');
    const button = screen.getByRole('button');
    userEvent.click(button);

    expect(mock).toHaveBeenCalled();
    expect(mock).toHaveBeenCalledWith({ name: 'jane', email: 'jane@jane.com' });
});

测试结果与错误信息

测试结果:

✓ it show two inputs and a button (84 ms)
✕ it calls onUserAdd when form submitted (41 ms)

错误信息:

● it calls onUserAdd when form submitted

expect(jest.fn()).toHaveBeenCalled()

Expected number of calls: >= 1
Received number of calls:    0

  23 |     userEvent.click(button);
  24 |
> 25 |     expect(mock).toHaveBeenCalled();
     |                  ^
  26 |     expect(mock).toHaveBeenCalledWith({ name: 'jane', email: 'jane@jane.com' });
  27 | });
  28 |

问题原因与解决方案

原因

userEvent v14+版本中,所有用户交互方法(如click、type)都改为异步方法,但测试中没有等待这些操作完成就执行断言,导致表单提交的handleSubmit逻辑还未执行,mock函数自然没有被调用。

解决方案

将测试函数改为异步函数,并用await等待所有用户交互操作完成,同时推荐使用userEvent.setup()创建用户实例(新版本最佳实践):

修改后的测试用例:

test('it calls onUserAdd when form submitted', async () => {
    const mock = jest.fn();
    const user = userEvent.setup();

    render(<UserForm onUserAdd={mock} />);
    const nameInput = screen.getByRole('textbox', { name: /name/i });
    const emailInput = screen.getByRole('textbox', { name: /email/i });
    
    await user.type(nameInput, 'jane');
    await user.type(emailInput, 'jane@jane.com');
    
    const button = screen.getByRole('button');
    await user.click(button);

    expect(mock).toHaveBeenCalled();
    expect(mock).toHaveBeenCalledWith({ name: 'jane', email: 'jane@jane.com' });
});

如果未使用setup,也可以直接await userEvent.click(button),但前者是新版本推荐的写法。

补充说明

如果是userEvent v13及以下版本,交互方法是同步的,但新版本已全面改为异步,这是这类测试失败的常见坑点。确保所有用户操作都用await等待,才能保证断言时相关业务逻辑已经执行完毕。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 11:25:58