Vitest搭配React Testing Library时userEvent.click无法触发表单提交
使用开箱即用的Create React App时,以下测试可正常运行并通过:
import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import App from "./App"; describe("Update the name", () => { it("updates the name", () => { render(<App />); userEvent.type(screen.getByLabelText(/name/i), "John"); userEvent.click(screen.getByRole("button")); expect(screen.getByText("John")).toBeInTheDocument(); }); });
上述测试逻辑本身无误:页面仅存在1个设置了type="submit"的按钮,应用在浏览器中运行正常,在Create React App(CRA)环境下测试可顺利通过。
迁移至基于Vite的Vitest测试框架时,理论上无需修改上述测试代码,但实际运行出现报错:TypeError: target.ownerDocument.createRange is not a function。
vite.config.js配置内容为:
/// <reference types="vitest" /> /// <reference types="vite/client" /> import react from "@vitejs/plugin-react"; import { defineConfig } from "vite"; export default defineConfig({ plugins: [react()], test: { globals: true, environment: "happy-dom", setupFiles: "./src/setup.js", }, });
./src/setup.js仅包含一行代码:import "happy-dom";
已尝试的排查方案:
- 切换测试环境为
jsdom - 将
userEvent.click替换为fireEvent.click - 给表单添加
data-testid属性后直接调用fireEvent.submit触发表单提交
后几种方案运行时无报错,但要么完全未触发表单提交逻辑,要么提交触发后组件渲染未按预期更新。
由于相同测试逻辑在CRA中可正常运行,测试代码本身无需修改,问题出在配置层面。
问题由两个核心配置/版本兼容问题导致,按以下步骤修正即可正常运行:
修正环境配置错误
移除./src/setup.js中的import "happy-dom";代码。当vitest配置中已指定environment: "happy-dom"时,vitest会自动完成测试环境的初始化与DOM上下文注入,手动导入happy-dom会造成DOM实例重复、上下文不匹配,是触发createRange is not a function报错的核心原因。
如果需要切换到jsdom环境,需先单独安装jsdom依赖:npm install -D jsdom,vitest 0.23及以上版本不再内置jsdom,未安装直接切换环境会导致DOM API缺失,出现事件不触发、渲染不更新的问题。适配@testing-library/user-event v14+ 异步API
CRA默认集成的user-event为v13及以下同步版本,Vite生态下默认安装的是v14+版本,该版本所有交互API均为异步实现,必须配合async/await调用,否则事件队列不会按预期执行,就会出现点击、输入后提交不触发、渲染不更新的问题。修正后的测试代码如下:
import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import App from "./App"; describe("Update the name", () => { it("updates the name", async () => { const user = userEvent.setup(); render(<App />); await user.type(screen.getByLabelText(/name/i), "John"); await user.click(screen.getByRole("button")); expect(screen.getByText("John")).toBeInTheDocument(); }); });
- (可选)补全happy-dom缺失API补丁
如果使用happy-dom时仍存在API缺失问题,可以在setup.js中添加createRange方法的polyfill:
if (!window.document.createRange) { window.document.createRange = () => ({ setStart: () => {}, setEnd: () => {}, commonAncestorContainer: { nodeName: "BODY", ownerDocument: document, }, }); }
内容的提问来源于stack exchange,提问作者CodeFinity

