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

React组件单元测试:验证Firebase sendPasswordResetEmail调用及Mock问题

问题

我正在开发基于React和Firebase后端的Web应用,React和Firebase功能实现没问题,现在借这个项目学习React Testing Library和Jest做单元测试。目前已经能测试表单渲染、提交无效/缺失邮箱时的警告UI,但测试提交有效邮箱时Firebase的sendPasswordResetEmail是否被调用时,出现错误:TypeError: (0 , _auth.sendPasswordResetEmail)(...).then is not a function。想确认是否正确使用了jest.fn().mockResolvedValue,以及如何解决这个问题。

组件代码

import React, { useState } from "react";
import { auth } from "../../lib/firebaseApp";
import { sendPasswordResetEmail } from "firebase/auth";

const PasswordResetForm = (props) => {
  const [email, setEmail] = useState(null);

  const handleChange = (e) => {
    setEmail(e.target.value);
  };

  const handleSubmit = (e) => {
    e.preventDefault();

    sendPasswordResetEmail(auth, email)
      .then(() => {
        //Email sent
      })
      .catch((error) => {
        console.log(error);
      });
  };

  return (
    <form noValidate onSubmit={handleSubmit}>
      <input type="text" name="email" value={email || ""} onChange={handleChange} />
      <SubmitButton buttonText="Reset password" />
    </form>
  );
};

export default PasswordResetForm;

尝试的测试代码

import React from "react";
import { render, fireEvent, screen } from "@testing-library/react";
import { BrowserRouter } from "react-router-dom";
import userEvent from "@testing-library/user-event";
import "@testing-library/jest-dom";

import { getAuth, sendPasswordResetEmail } from "firebase/auth";

//Component
import PasswordResetForm from "../../../components/forms/passwordresetform";


//Mock firebase
jest.mock('firebase/auth', () => {
  return {
    getAuth: () => jest.fn(),
    sendPasswordResetEmail: () => jest.fn().mockResolvedValue({}),
  }
});



//UI tests
describe("Password reset form", () => {
  it("Should allow good email addresses and call sendPasswordResetEmail", async () => {
    render(<PasswordResetForm />, { wrapper: BrowserRouter });
    const input = screen.getByRole("textbox", { name: "email" });
    const button = screen.getByRole("button", { name: "submit" });
    const value = "user@domain.com";
    userEvent.type(input, value);
    fireEvent.click(button);
    expect(sendPasswordResetEmail).toBeCalledWith(getAuth(), value);
  });
});

解决方案

问题根源

你的mock写法存在错误:sendPasswordResetEmail: () => jest.fn().mockResolvedValue({}) 这行代码返回的是一个函数工厂,而非直接返回Promise的mock函数。组件调用sendPasswordResetEmail(auth, email)时,实际得到的是另一个jest函数,不是Promise,自然没有.then方法,因此触发报错。

修复步骤

  1. 修正Firebase Auth的mock定义
    把sendPasswordResetEmail直接定义为返回Promise的mock函数,同时调整getAuth或直接mock组件中导入的auth实例:

    jest.mock('firebase/auth', () => {
      return {
        sendPasswordResetEmail: jest.fn().mockResolvedValue({}),
      }
    });
    
    // 额外mock组件中导入的auth实例,确保调用参数一致
    jest.mock('../../../lib/firebaseApp', () => ({
      auth: {}
    }));
    
  2. 对齐测试中的断言参数
    组件里使用的是从firebaseApp导入的auth实例,测试里要复用这个mock实例,避免参数不匹配:

    import { auth } from "../../../lib/firebaseApp";
    // ...
    expect(sendPasswordResetEmail).toHaveBeenCalledWith(auth, value);
    
  3. 优化测试流程(可选)
    由于sendPasswordResetEmail是异步操作,建议用waitFor包裹断言,确保异步逻辑完成后再验证:

    await waitFor(() => {
      expect(sendPasswordResetEmail).toHaveBeenCalledWith(auth, value);
    });
    

完整修复后的测试代码

import React from "react";
import { render, fireEvent, screen, waitFor } from "@testing-library/react";
import { BrowserRouter } from "react-router-dom";
import userEvent from "@testing-library/user-event";
import "@testing-library/jest-dom";

import { sendPasswordResetEmail } from "firebase/auth";
import { auth } from "../../../lib/firebaseApp";

//Component
import PasswordResetForm from "../../../components/forms/passwordresetform";

//Mock firebase auth
jest.mock('firebase/auth', () => {
  return {
    sendPasswordResetEmail: jest.fn().mockResolvedValue({}),
  }
});

//Mock firebaseApp里的auth实例
jest.mock('../../../lib/firebaseApp', () => ({
  auth: {}
}));

//UI tests
describe("Password reset form", () => {
  it("Should call sendPasswordResetEmail with valid email", async () => {
    render(<PasswordResetForm />, { wrapper: BrowserRouter });
    const input = screen.getByRole("textbox", { name: "email" });
    const button = screen.getByRole("button", { name: /reset password/i });
    const testEmail = "user@domain.com";
    
    userEvent.type(input, testEmail);
    fireEvent.submit(screen.getByRole('form'));
    
    await waitFor(() => {
      expect(sendPasswordResetEmail).toHaveBeenCalledWith(auth, testEmail);
    });
  });
});

额外说明

  • 之前测试中的按钮选择器{ name: "submit" }不符合组件实际文本,改用正则/reset password/i匹配更可靠。
  • 触发表单的submit事件比直接点击按钮更贴近真实用户行为,避免遗漏表单的默认提交逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 13:40:27