Search code examples
reactjsfirebase

How do I resolve TypeError: _app.default.auth is not a function error


I need help resolving error when running unit tests on a hook being used to retrieve user data.

TypeError: _app.default.auth is not a function

  10 |       setUserId(storedUserId);
  11 |     } else {
> 12 |       const unsubscribe = firebase.auth().onAuthStateChanged((user) => {

I'm only getting the above error when running unit tests for a hook I created to retrieve user info from storage or Firebase. This hook looks in storage and then Firebase if storage === null.

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;
// 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);

And unit test:

// 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: '[email protected]',
      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);
  });

I'm only getting the error when running the unit test, I think there must be an error on how I set up my Firebase config, but I'm not sure.

The actual error is it's displaying is in a separte hook that gets the UserId, however the unit tests for that hook is not failing.

Here's the actual line of code that's causing errors in a separate Hook's unit test.

  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;
};

Solution

  • Based on the code you provided, as long as local storage contains an entry for "USER_CREDENTIALS", the logic that is throwing the error is skipped entirely.

    When you mock localStorage.getItem() to always return null, you now cause the faulty logic to execute.

    useEffect(() => {
      const storedUserId = localStorage.getItem("USER_CREDENTIALS");
      if (storedUserId) { // this becomes null
        setUserId(storedUserId);
      } else { // this block gets executed
        const unsubscribe = firebase.auth().onAuthStateChanged((user) => { // this line throws the error
          if (user) {
            const uid = user.uid;
            localStorage.setItem("USER_CREDENTIALS", uid);
            setUserId(uid);
          }
        });
        return () => unsubscribe();
      }
    }, [userId]);
    

    To update this legacy logic to the new syntax, you'd use the following:

    import { getAuth, onAuthStateChanged } from "firebase/auth";
    
    useEffect(() => {
      const storedUserId = localStorage.getItem("USER_CREDENTIALS");
      if (storedUserId) {
        setUserId(storedUserId);
      } else {
        const unsubscribe = onAuthStateChanged(getAuth(), (user) => { // line updated to new syntax
          if (user) {
            const uid = user.uid;
            localStorage.setItem("USER_CREDENTIALS", uid);
            setUserId(uid);
          }
        });
        return () => unsubscribe();
      }
    }, [userId]);