Jest SpyOn返回undefined问题:React18无Enzyme测试报错排查
问题分析与解决方案
错误原因
你遇到的Cannot spyOn on a primitive value; undefined given报错,核心原因有两个:
- 函数组件无prototype属性:
EmailForm是函数式组件,并非类组件,EmailForm.prototype的值为undefined,Jest的spyOn无法对undefined执行监听操作。 - 内部闭包函数无法被外部捕获:
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监听:
- 重构组件,抽离函数:
// 抽离到外部工具文件(如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); } }} // ...其他属性 /> ); };
- 测试中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
相关产品推荐
相关产品推荐

