React Testing Library状态变更后无法找到导入的WarningDialog组件
问题描述
我使用Formik和Yup构建了一个表单,当提交表单时若邮箱为空,会基于组件状态isWarning显示一个WarningDialog模态框。测试时,我点击提交按钮提交未填写邮箱的表单,期望看到模态框中的文本,但测试因无法找到该元素而失败。
表单代码
import React, { useMemo } from "react" import { TextField, Divider, Chip, Button, useMediaQuery, useTheme, } from "@mui/material" import { useTranslation } from "react-i18next" import { object, string, number } from "yup" import { useFormik } from "formik" import { useApplication } from "../../framework/useApplication" import { Container, Outer, Inner } from "./styles" import WarningDialog from "./WarningDialog" const UserPreferences = () => { const { user } = useApplication() const { t } = useTranslation() const [isWarning, setIsWarning] = React.useState(false) const theme = useTheme() const fullScreen = useMediaQuery(theme.breakpoints.down("sm")) const validationSchema = useMemo( () => object({ title: string(), firstName: string(), lastName: string(), email: string().email("Please enter a valid email"), jobTitle: string(), personalPhone: number().typeError( "Please enter a valid number", ), mobilePhone: number().typeError("Please enter a valid number"), }), [t], ) const handleSubmit = values => { if (values.email === "") { setIsWarning(true) } else { submitForm(values) setIsWarning(false) } } const submitForm = values => { console.log(values) setIsWarning(false) } const formik = useFormik({ initialValues: { title: "", firstName: "", lastName: "", email: "", jobTitle: "", personalPhone: "", mobilePhone: "", }, validationSchema: validationSchema, onSubmit: values => { handleSubmit({ title: values.title, firstName: values.firstName, lastName: values.lastName, email: values.email, jobTitle: values.jobTitle, personalPhone: values.personalPhone, mobilePhone: values.mobilePhone, }) }, }) return ( <div> <Container> <Divider> <Chip label={`${t("common.details")}`} /> </Divider> <Outer> <span>User Name: {user.uname}</span> <span>User ID: {user.userId}</span> </Outer> <form onSubmit={formik.handleSubmit}> <Outer> <Inner> <TextField id="title" name="title" label="title" value={formik.values.title} onChange={formik.handleChange} variant="outlined" placeholder="Title" error={ formik.touched.title && Boolean(formik.errors.title) } helperText={ formik.touched.title && formik.errors.title } /> <TextField id="firstName" name="firstName" label="first name" value={formik.values.firstName} onChange={formik.handleChange} variant="outlined" placeholder="First Name" error={ formik.touched.firstName && Boolean(formik.errors.firstName) } helperText={ formik.touched.firstName && formik.errors.firstName } /> <TextField id="lastName" name="lastName" label="last name" value={formik.values.lastName} onChange={formik.handleChange} variant="outlined" placeholder="Last Name" error={ formik.touched.lastName && Boolean(formik.errors.lastName) } helperText={ formik.touched.lastName && formik.errors.lastName } /> <TextField id="email" name="email" label="email" value={formik.values.email} onChange={formik.handleChange} variant="outlined" placeholder="Email" error={ formik.touched.email && Boolean(formik.errors.email) } helperText={ formik.touched.email && formik.errors.email } /> </Inner> <Inner> <TextField id="jobTitle" name="jobTitle" label="job title" value={formik.values.jobTitle} onChange={formik.handleChange} variant="outlined" placeholder="Job Title" /> <TextField id="personalPhone" name="personalPhone" label="personal phone" value={formik.values.personalPhone} onChange={formik.handleChange} variant="outlined" placeholder="Personal Phone" error={ formik.touched.personalPhone && Boolean(formik.errors.personalPhone) } helperText={ formik.touched.personalPhone && formik.errors.personalPhone } /> <TextField id="mobilePhone" name="mobilePhone" label="mobile phone" value={formik.values.mobilePhone} onChange={formik.handleChange} variant="outlined" placeholder="Mobile Phone" error={ formik.touched.mobilePhone && Boolean(formik.errors.mobilePhone) } helperText={ formik.touched.mobilePhone && formik.errors.mobilePhone } /> </Inner> </Outer> <Outer> <Button type="submit">{t("common.save")}</Button> </Outer> </form> </Container> {isWarning && ( <WarningDialog handleClose={() => setIsWarning(false)} handleSubmit={() => submitForm(formik.values)} open={isWarning} fullScreen={fullScreen} /> )} </div> ) } export default UserPreferences
测试代码
import React from "react" import { render, screen, fireEvent, waitFor } from "@testing-library/react" import UserPreferences from "./UserPreferences" import { vi } from "vitest" vi.mock("../../framework/useApplication", () => ({ useApplication: () => ({ user: { uname: "testUser", userId: "123" } }), })) vi.mock("useTranslation", () => ({ useTranslation: () => ({ t: vi.fn() }), })) vi.mock("./WarningDialog", () => { return { default: () => <div>Email field is blank</div>, } }) vi.mock("formik", () => ({ useFormik: () => ({ values: { title: "", firstName: "", lastName: "", email: "", jobTitle: "", personalPhone: "", mobilePhone: "", }, errors: {}, touched: {}, handleChange: vi.fn(), handleSubmit: vi.fn(), }), })) describe("UserPreferences", () => { beforeEach(() => { vi.clearAllMocks() }) test("displays warning dialog when submitting with empty email", async () => { render(<UserPreferences />) const submitButton = screen.getByRole("button", { name: /save/i }) // Submit form with empty email fireEvent.click(submitButton) // Verify warning dialog is displayed expect( await screen.findByText("Email field is blank"), ).toBeInTheDocument() }) })
错误信息
TestingLibraryElementError: Unable to find an element with the text: Email field is blank.
解决方案
问题原因
测试中mock的useFormik返回的handleSubmit是一个空的mock函数,没有触发组件中定义的onSubmit回调,导致handleSubmit函数从未执行,isWarning状态不会被设置为true,模态框自然不会渲染。另外,mock的useTranslation返回的t函数没有返回实际文本,导致按/save/i查找按钮可能失败。
修复步骤
1. 修正Formik的mock,让handleSubmit触发提交逻辑
修改formik的mock,让handleSubmit调用传入的回调函数,这样组件里的onSubmit逻辑才能执行:
vi.mock("formik", () => ({ useFormik: () => ({ values: { title: "", firstName: "", lastName: "", email: "", jobTitle: "", personalPhone: "", mobilePhone: "", }, errors: {}, touched: {}, handleChange: vi.fn(), // 调用传入的回调,触发组件的onSubmit逻辑 handleSubmit: (callback) => callback(), }), }))
2. 修正i18n的mock,让按钮文本可被查找
修改useTranslation的mock,让t函数返回对应键的文本,确保能找到提交按钮:
vi.mock("useTranslation", () => ({ useTranslation: () => ({ t: (key) => { if (key === "common.save") return "Save"; return ""; } }), }))
修正后的完整测试代码
import React from "react" import { render, screen, fireEvent, waitFor } from "@testing-library/react" import UserPreferences from "./UserPreferences" import { vi } from "vitest" vi.mock("../../framework/useApplication", () => ({ useApplication: () => ({ user: { uname: "testUser", userId: "123" } }), })) vi.mock("useTranslation", () => ({ useTranslation: () => ({ t: (key) => key === "common.save" ? "Save" : "" }), })) vi.mock("./WarningDialog", () => { return { default: () => <div>Email field is blank</div>, } }) vi.mock("formik", () => ({ useFormik: () => ({ values: { title: "", firstName: "", lastName: "", email: "", jobTitle: "", personalPhone: "", mobilePhone: "", }, errors: {}, touched: {}, handleChange: vi.fn(), handleSubmit: (callback) => callback(), }), })) describe("UserPreferences", () => { beforeEach(() => { vi.clearAllMocks() }) test("displays warning dialog when submitting with empty email", async () => { render(<UserPreferences />) const submitButton = screen.getByRole("button", { name: /save/i }) fireEvent.click(submitButton) expect(await screen.findByText("Email field is blank")).toBeInTheDocument() }) })
这样修改后,测试就能正常触发提交逻辑,设置isWarning状态,渲染模态框,从而找到对应的文本元素。
内容的提问来源于stack exchange,提问作者Mauro74
相关产品推荐
相关产品推荐

