Search code examples
reactjsfirebasejestjsreact-testing-libraryreact-hooks-testing-library

How to mock a function that will have then and catch block?


I using Firebase authentication and I would like to test the function using Jest and react-hooks-testing-library.

I have a function that like this:

const loginWithEmailPassword = (email: string, password: string) => {

    const auth = getAuth()

    signInWithEmailAndPassword(auth, email, password)
        .then((userCredential) => {
            // Signed in 
            const user = userCredential.user;
            // ...
        }).catch((error) => {
            const errorCode = error.code;
            const errorMessage = error.message;
        });

}

The signInWithEmailPassword() function have a then() and catch() block.

I have mock the function with this code:

const mockSignUp = jest.fn(() => {
    return Promise.resolve({
        user: {
            uid: "fakeuid",
        },
    });
})

jest.mock('firebase/auth', () => ({
    getAuth: () => mockGetAuth,
    signInWithEmailAndPassword: () => mockSignIn,
    createUserWithEmailAndPassword: () => mockSignUp
}))

Then I test the function above using react-hooks-testing-library which like this:

 test('Login with Email and Password', () => {
    const { result } = renderHook(() => useFirebaseAuth())

    const email = 'abc@gmail.com'
    const password = '123456'
  
    // here fired my loginWithEmailPassword above
    act(() => {
        // the problem come from this line
        result.current.loginWithEmailPassword(email, password)
    })

Then my test failed with this error:

 TypeError: (0 , _auth.signInWithEmailAndPassword)(...).then is not a function

      46 |
      47 |         signInWithEmailAndPassword(auth, email, password)
    > 48 |             .then((userCredential) => {

If I remove the then block, the test passed. But if I use called the then() for the function it get the error. I check that my mock for signInWithEmailAndPassword with return of Promise.resolve() should be ok, but it still having the error.

I am new in testing field. Please somebody give some suggestion on this and tell me what is wrong with my test? I have absolutely no idea

I have tried to mock it like this, after looking for this answer:

const mockSomething = jest.fn().mockImplementation(() => Promise.resolve({
    user: {
        uid: "fakeuid",
    },
}))

But still having the same error


Solution

  • Finally I able to solve the problem by mock a function with will return promise that resolved with user like this:

    jest.mock('firebase/auth', () => {
        return {
            getAuth: () => mockGetAuth,
             
            // since this method from firebase return a promise, here need a promise as well 
            signInWithEmailAndPassword: jest.fn().mockResolvedValue({
               user: {
                   uid: "fakeUid",
                },
              }),
            createUserWithEmailAndPassword: jest.fn().mockResolvedValue({
                 user: {
                   uid: "fakeUid",
                 },
              }),
        }
    })
    

    in the signInWithEmailPassword , I direct return the Promise without wrap it with jest.fn()

    Then in test, I can do this:

    import { signInWithEmailAndPassword, getAuth } from 'firebase/auth';
    
    // here I can check that `signInWithEmailPassword is call with some value
    expect(signInWithEmailAndPassword).toBeCalledWith(
      getAuth(),
      email,
      password
    );
    
    // Here I can compare my UI to the fakeUid as well 
    expect(data.user.uid).toEqual('fakeUid')
    

    In test, I have to call signInWithEmailAndPassword which is direct from firebase/auth.

    Since I already mock the function in jest.mock(), therefore in test it will return back the fake data I defined directly, which is data: { user: { uid: 'fakeUid' } }

    The whole process is like:

    • There are an actual function in firebase/auth module named signInWithEmailPassword
    • But I dont want to "really" call that, therefore I make a fake function with same name that will return the same thing(Promise in this case) of the actual function inside jest.mock()
    • Now, in test section, I call the function direct from Firebase, but now instead of "actual" calling it from Firebase, it just return the "fake" Promise with the fake data that I defined just now.
    • Then I can compare it to a value by using expect(data.user.uid).toEqual('fakeUid')