Vitest+React Testing Library测试时,表单提交Mock函数未触发问题
问题
我正尝试为一个接收handleSubmit作为props的React组件编写测试,该组件会在表单提交的回调中调用此handleSubmit。我使用了vitest和react-testing-library,组件代码如下:
const APIKeyInput = ({ handleSubmit }: APIKeyInputProps) => { const onSubmit = (event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); handleSubmit(event.currentTarget.elements.apiKey.value); }; return ( <form onSubmit={onSubmit}> <label htmlFor="apiKey">Insira sua chave para a API:</label> <input type="text" name="apiKey" /> <input type="submit" value="Entrar" /> </form> ); };
在测试中,我用vi.fn()创建了一个Mock函数,并将其作为handleSubmit props传入,测试代码如下:
import { describe, it, expect, vi } from "vitest"; import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import APIKeyInput from "./App"; describe("APIKeyInput", () => { it("calls onSubmit when submitted", async () => { const user = userEvent.setup(); const onSubmitMock = vi.fn(); render(<APIKeyInput handleSubmit={onSubmitMock} />); expect(onSubmitMock).not.toHaveBeenCalled(); const apiSubmit = screen.getByRole("button"); await user.click(apiSubmit); await waitFor(() => { expect(onSubmitMock).toHaveBeenCalledTimes(1); }); }); });
通过日志可以看到,当触发user.click(apiSubmit)时,组件内的onSubmit函数已被调用,但onSubmitMock却未被触发,请问我遗漏了什么?
解决方案
核心问题是你直接通过event.currentTarget.elements.apiKey.value获取输入值的方式存在隐患——当测试中点击提交按钮时,这个DOM元素的引用可能无法被正确解析,导致代码在调用handleSubmit前就静默出错了(错误没冒泡到测试层面,所以你只看到组件内的onSubmit执行了,但handleSubmitMock没触发)。
可以试试这两种修复方式:
方式一:改成受控组件(推荐)
用React state管理输入框的值,彻底避免直接操作DOM,这也是React的标准写法:
import { useState } from 'react'; const APIKeyInput = ({ handleSubmit }: APIKeyInputProps) => { const [apiKey, setApiKey] = useState(''); const onSubmit = (event: React.FormEvent<HTMLFormElement>) => { event.preventDefault(); handleSubmit(apiKey); }; return ( <form onSubmit={onSubmit}> <label htmlFor="apiKey">Insira sua chave para a API:</label> <input type="text" name="apiKey" value={apiKey} onChange={(e) => setApiKey(e.target.value)} /> <input type="submit" value="Entrar" /> </form> ); };
这种写法不仅让测试更稳定,也符合React的数据流设计。
方式二:调整测试逻辑(不修改组件的前提下)
如果不想改组件,需要确保测试中先填入输入值,并且改用user.submit()直接提交表单(比点击按钮更可靠):
it("calls onSubmit when submitted", async () => { const user = userEvent.setup(); const onSubmitMock = vi.fn(); render(<APIKeyInput handleSubmit={onSubmitMock} />); expect(onSubmitMock).not.toHaveBeenCalled(); // 先找到输入框并填入测试值 const apiInput = screen.getByLabelText(/Insira sua chave para a API:/i); await user.type(apiInput, 'test-api-key'); // 直接提交表单 const form = screen.getByRole('form'); await user.submit(form); // 不需要waitFor,user.submit是异步操作,等待完成后直接断言 expect(onSubmitMock).toHaveBeenCalledTimes(1); expect(onSubmitMock).toHaveBeenCalledWith('test-api-key'); });
内容的提问来源于stack exchange,提问作者Felipe Martins
相关产品推荐
相关产品推荐

