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

React单元测试中Firebase Mock返回null导致测试失败的问题求助

React单元测试中Firebase Mock返回null导致测试失败的问题求助

我帮你梳理下测试失败的核心原因,然后一步步给出解决办法:

先分析为什么测试会失败

  1. 没有Mock useGetUserId 钩子:你的useGetUserProfile依赖useGetUserId获取用户ID,但测试里完全没处理这个钩子,默认它返回null,导致钩子直接跳过了Firebase请求逻辑,userProfile自然一直是null。
  2. waitFor 使用错误:你只写了waitFor但没加await,也没等待具体状态变化,测试代码会在异步请求完成前就执行断言,这时候数据还没加载好。
  3. useEffect 依赖数组不全:你只加了setUserProfile,但核心依赖userId没加入,就算userId后来有值了,useEffect也不会重新触发请求,这既是钩子本身的bug,也影响测试结果。
  4. Firebase App重复初始化(次要但关键):钩子每次进入else分支都会重新初始化Firebase App,实际运行时会抛出重复初始化的错误,这也会导致请求失败。

具体解决步骤

1. 先修正钩子本身的问题

打开你的useGetUserProfile钩子,做两个关键修改:

  • 把userId加入useEffect的依赖数组,确保userId变化时能重新触发请求:
useEffect(() => {
  // 内部逻辑保持不变
}, [userId, setUserProfile]); // 新增userId作为依赖
  • 避免重复初始化Firebase App,用getApps检查是否已有初始化的App:
// 先导入getApps方法
import { getApps, initializeApp } from "firebase/app";

// 修改else分支的代码:
else {
  // 检查是否已有初始化的App,没有再创建
  const app = getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0];
  const db = getFirestore(app);
  if (userId) {
    const id = JSON.parse(userId);
    const userRef = await getDoc(doc(db, "users", id.user.uid));
    if (userRef.exists()) {
      const profile = userRef.data();
      setUserProfile(profile);
      // 顺便把获取到的profile存入localStorage,下次就不用再请求了
      localStorage.setItem("PROFILE_INFO", JSON.stringify(profile));
    }
  }
}

2. 重构测试代码,解决Mock和异步等待问题

修改你的测试文件,补上缺失的Mock和正确的异步等待逻辑:

import { renderHook, waitFor } from "@testing-library/react";
import useGetUserProfile from "../../hooks/useGetUserProfile";
import useGetUserId from "../../hooks/useGetUserId"; // 引入要Mock的钩子
import { getDoc, doc } from "firebase/firestore";

// Mock useGetUserId 钩子
jest.mock('../../hooks/useGetUserId', () => ({
  __esModule: true,
  default: jest.fn(),
}));

// Mock Firebase的firestore方法
jest.mock('firebase/firestore', () => ({
  getDoc: jest.fn(),
  doc: jest.fn(() => ({})), // 让doc返回空对象即可,因为getDoc已经被Mock了
}));

const mockProfile = {
    email: 'test@example.com',
    username: 'testuser',
    userBio: 'Bio',
    dob: new Date(),
    gender: 'Male',
    sexo: 'M',
    education: 'University',
    drinkingHabits: 'Social',
    smokingHabits: 'No',
  };

beforeEach(() => {
  jest.clearAllMocks();
  // 让useGetUserId返回一个有效的用户ID(和你钩子内的解析逻辑匹配)
  (useGetUserId as jest.Mock).returnValue = JSON.stringify({ user: { uid: 'test-user-id-123' } });
  // 每次测试前清空localStorage,避免测试间互相影响
  localStorage.clear();
})

describe("useGetUserProfile", () => {
  it("should fetch profile from Firebase when local storage is empty", async () => {
    // Mock getDoc返回我们预设的profile
    getDoc.mockResolvedValue({
      exists: () => true,
      data: () => mockProfile,
    });

    const { result } = renderHook(() => useGetUserProfile());

    // 首先断言初始状态是加载中
    expect(result.current.isLoading).toBe(true);

    // 等待加载完成,或者userProfile不为null
    await waitFor(() => {
      expect(result.current.isLoading).toBe(false);
    });

    // 断言最终的userProfile和我们预设的一致
    expect(result.current.userProfile).toEqual(mockProfile);
    // 顺便断言getDoc被调用了一次
    expect(getDoc).toHaveBeenCalledTimes(1);
  });

  it("should load profile from local storage when it exists", async () => {
    // 先在localStorage存入测试用的profile
    localStorage.setItem("PROFILE_INFO", JSON.stringify(mockProfile));

    const { result } = renderHook(() => useGetUserProfile());

    await waitFor(() => {
      expect(result.current.isLoading).toBe(false);
    });

    // 断言从localStorage加载的数据正确
    expect(result.current.userProfile).toEqual(mockProfile);
    // 断言没有调用Firebase的getDoc
    expect(getDoc).not.toHaveBeenCalled();
  });
});

为什么这些修改能解决问题?

  • Mock useGetUserId后,钩子能拿到有效的用户ID,才会进入Firebase请求的分支;
  • 用await waitFor(...)确保测试等待异步操作完成后再断言,不会再拿到未初始化的null;
  • 补上userId依赖后,钩子能正确响应userId的变化,实际使用和测试时都能正常触发请求;
  • 修复Firebase重复初始化的问题,避免实际运行时的错误,也让测试更稳定。

备注:内容来源于stack exchange,提问作者D.Hodges

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 09:38:08