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

Jest SpyOn返回undefined问题:React18无Enzyme测试报错排查

问题分析与解决方案

错误原因

你遇到的Cannot spyOn on a primitive value; undefined given报错,核心原因有两个:

  1. 函数组件无prototype属性:EmailForm是函数式组件,并非类组件,EmailForm.prototype的值为undefined,Jest的spyOn无法对undefined执行监听操作。
  2. 内部闭包函数无法被外部捕获:handleAddEmail是组件内部定义的闭包函数,不属于组件对外暴露的属性或原型方法,外部无法通过spyOn直接监听它。

正确测试方案

React Testing Library的核心理念是测试用户行为而非组件内部实现,因此不建议直接监听内部方法,而是通过断言用户操作后的UI变化或副作用来验证功能。针对你的场景,可采用以下两种方案:

方案1:断言UI与状态变化(推荐)

测试用户输入邮箱并按回车后,验证输入框是否清空、收件人列表是否新增目标邮箱:

import { render, fireEvent, screen } from "@testing-library/react";
import EmailForm from "./EmailForm";

const mockSubmit = jest.fn();

const renderComponent = () => {
    return render(<EmailForm actionType="send_email" actionOrigin="Communications" handleActionFormSubmit={mockSubmit} />);
};

describe("EmailForm component", () => {
    it("adds email when Enter key is pressed", async () => {
        renderComponent();
        const recipientField = screen.getByTestId("recipient-field");
        const testEmail = "f@gmail.com";

        // 模拟用户输入并按回车
        fireEvent.change(recipientField, { target: { value: testEmail } });
        fireEvent.keyDown(recipientField, { key: "Enter" });

        // 断言输入框被清空
        expect(recipientField).toHaveValue("");
        // 断言收件人列表中出现新增邮箱(需确保列表项可通过文本或test-id定位)
        expect(screen.getByText(testEmail)).toBeInTheDocument();
    });
});

方案2:抽离内部函数以便监听(特殊场景用)

如果必须单独验证handleAddEmail的逻辑,可将其抽离为外部独立函数,再在测试中mock监听:

  1. 重构组件,抽离函数:
// 抽离到外部工具文件(如emailUtils.ts)
export const handleAddEmail = (newEmail: string, formik: FormikProps<FormValues>) => {
    if (!newEmail.trim()) {
        formik.setFieldError("recipient", t("FormValidation.Recipient_Required"));
        return;
    }

    if (!formik.values.recipients.includes(newEmail)) {
        formik.setFieldValue("recipients", [...formik.values.recipients, newEmail]);
        formik.setFieldValue("recipient", "");
    }
};

// 组件内部引入使用
import { handleAddEmail } from "./emailUtils";

const EmailForm = () => {
    // ...其他组件逻辑
    return (
        <TextField
            onKeyDown={(e) => {
                if (e.key === "Enter") {
                    handleAddEmail(formik.values.recipient, formik);
                }
            }}
            // ...其他属性
        />
    );
};
  1. 测试中mock并验证:
import { render, fireEvent, screen } from "@testing-library/react";
import EmailForm from "./EmailForm";
import * as emailUtils from "./emailUtils";

const mockSubmit = jest.fn();

const renderComponent = () => {
    return render(<EmailForm actionType="send_email" actionOrigin="Communications" handleActionFormSubmit={mockSubmit} />);
};

describe("EmailForm component", () => {
    it("triggers handleAddEmail on Enter key press", async () => {
        // mock抽离的函数
        const handleAddEmailSpy = jest.spyOn(emailUtils, "handleAddEmail").mockImplementation(() => {});
        renderComponent();
        const recipientField = screen.getByTestId("recipient-field");
        const testEmail = "f@gmail.com";

        fireEvent.change(recipientField, { target: { value: testEmail } });
        fireEvent.keyDown(recipientField, { key: "Enter" });

        expect(handleAddEmailSpy).toHaveBeenCalledWith(testEmail, expect.any(Object));
        // 清理mock
        handleAddEmailSpy.mockRestore();
    });
});

总结

优先选择方案1,遵循React Testing Library的测试原则,聚焦用户可见的行为结果;仅在需要单独验证复杂逻辑函数时,再考虑方案2的抽离方式。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 01:25:17