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

如何解决单元测试中的TypeError: _app.default.auth is not a function错误

自定义Hook单元测试报错:TypeError: _app.default.auth is not a function

TypeError: _app.default.auth is not a function

10 | setUserId(storedUserId);
11 | } else {

12 | const unsubscribe = firebase.auth().onAuthStateChanged((user) => {

仅在运行自定义Hook的单元测试时触发该错误,该Hook先从localStorage读取用户信息,若为空则从Firebase获取。报错实际来自useGetUserId Hook,但该Hook自身单元测试正常。


相关代码

useGetUserProfile Hook 代码

import { useEffect, useState } from "react";
import useGetUserId from "./useGetUserId";

import { app } from "../environments/environment";
import { doc, getDoc, getFirestore } from "firebase/firestore";

const useGetUserProfile = () => {
  type profile = {
    email: string;
    username: string;
    userBio: string;
    dob: Date;
    gender: string;
    sexo: string;
    education: string;
    drinkingHabits: string;
    smokingHabits: string;
  };

  const db = getFirestore(app);
  const userId: string | null = useGetUserId();
  const [isLoading, setIsLoading] = useState(true);
  const [userProfile, setUserProfile] = useState<any | null>(null);

  useEffect(() => {
    const userProfile = async () => {
      setIsLoading(true);
      try {
        const userRef = localStorage.getItem("PROFILE_INFO");
        if (userRef) {
          const profile: profile = JSON.parse(userRef);
          setUserProfile(profile);
        } else {
          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);
            }
          }
        }
      } catch (error) {
        console.log("error", error);
      } finally {
        setIsLoading(false);
      }
    };
    userProfile();
  }, [setUserProfile]);
  return {
    isLoading,
    userProfile, setUserProfile
  };
};

export default useGetUserProfile;

Firebase 环境配置代码

// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.

export const firebaseConfig = {
//config properties
};

// Initialize Firebase
export const app = initializeApp(firebaseConfig);

// Initialize Firebase Authentication and get a reference to the service
export const auth = getAuth(app);

单元测试代码

// Mocking Firebase Firestore methods
jest.mock('firebase/firestore', () => ({
  getFirestore: jest.fn(),
  doc: jest.fn(),
  getDoc: jest.fn(),
}));

// Mocking localStorage
beforeEach(() => {
  jest.spyOn(Storage.prototype, 'getItem').mockImplementation(() => null); // Reset localStorage
});

describe('useGetUserProfile', () => {
  it.only('should get user profile from localStorage if available', async () => {
    // Mock localStorage with a mock profile
    const mockProfile = {
      email: 'test@example.com',
      username: 'testuser',
      userBio: 'Bio info',
      dob: new Date('1990-01-01'),
      gender: 'Male',
      sexo: 'M',
      education: 'Bachelor',
      drinkingHabits: 'Occasionally',
      smokingHabits: 'Non-smoker',
    };
    jest.spyOn(Storage.prototype, 'getItem').mockImplementationOnce(() => JSON.stringify(mockProfile));

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

    expect(result.current.userProfile).toEqual(mockProfile);
  });

报错的 useGetUserId Hook 代码片段

useEffect(() => {
    const storedUserId = localStorage.getItem("USER_CREDENTIALS");
    if (storedUserId) {
      setUserId(storedUserId);
    } else {
      const unsubscribe = firebase.auth().onAuthStateChanged((user) => {
        if (user) {
          const uid = user.uid;
          localStorage.setItem("USER_CREDENTIALS", uid);
          setUserId(uid);
        }
      });

      return () => unsubscribe();
    }
  }, [userId]);

  return userId;
};

问题原因与解决方法

原因分析

报错核心是单元测试环境中firebase.auth()未被正确mock:当前测试仅mock了Firestore相关方法,但useGetUserId Hook会调用Firebase Auth的auth()和onAuthStateChanged方法,测试环境未提供这些mock实现,导致调用时抛出函数不存在的错误。

另外,useGetUserId Hook中直接使用firebase.auth(),但Firebase配置文件已导出初始化好的auth实例,实际代码可优化为直接导入使用,避免重复调用。

解决步骤

  1. 在单元测试中mock Firebase Auth模块
    在现有测试代码中添加Firebase Auth的mock,确保auth()方法返回包含onAuthStateChanged的对象:

    // 新增Firebase Auth的mock
    jest.mock('firebase/auth', () => ({
      getAuth: jest.fn(() => ({
        onAuthStateChanged: jest.fn(() => jest.fn()) // 返回可取消订阅的函数
      }))
    }));
    
  2. 修复useGetUserId Hook的Firebase Auth调用方式
    直接导入配置文件中已初始化的auth实例,避免重复调用firebase.auth():

    import { auth } from "../environments/environment"; // 导入已初始化的auth实例
    
    // ... 其他代码
    useEffect(() => {
      const storedUserId = localStorage.getItem("USER_CREDENTIALS");
      if (storedUserId) {
        setUserId(storedUserId);
      } else {
        // 使用导入的auth实例,而非firebase.auth()
        const unsubscribe = auth.onAuthStateChanged((user) => {
          if (user) {
            const uid = user.uid;
            localStorage.setItem("USER_CREDENTIALS", uid);
            setUserId(uid);
          }
        });
    
        return () => unsubscribe();
      }
    }, [userId]);
    
  3. 确保localStorage的mock覆盖所有情况
    当前测试仅mock了PROFILE_INFO,但useGetUserId会读取USER_CREDENTIALS,可在测试中同时mock该值,避免进入Firebase Auth分支:

    it.only('should get user profile from localStorage if available', async () => {
      // Mock localStorage with a mock profile
      const mockProfile = {
        email: 'test@example.com',
        username: 'testuser',
        userBio: 'Bio info',
        dob: new Date('1990-01-01'),
        gender: 'Male',
        sexo: 'M',
        education: 'Bachelor',
        drinkingHabits: 'Occasionally',
        smokingHabits: 'Non-smoker',
      };
      // 同时mock USER_CREDENTIALS,避免触发Firebase Auth逻辑
      jest.spyOn(Storage.prototype, 'getItem')
        .mockImplementation((key) => {
          if (key === 'PROFILE_INFO') return JSON.stringify(mockProfile);
          if (key === 'USER_CREDENTIALS') return 'mock-uid';
          return null;
        });
    
      const { result } = renderHook(() => useGetUserProfile());
    
      expect(result.current.userProfile).toEqual(mockProfile);
    });
    

内容的提问来源于stack exchange,提问作者D.Hodges

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 11:10:04