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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 09:37:02