Dustwise
Dustwise

Reputation: 677

How to mock Firebase Auth methods? (React, Testing Library)

I have the following component:

import React from "react";
import Firebase from "../../Firebase";

const SignOutButton = () => (
  <button type="button" onClick={() => Firebase.auth().signOut()}>
    Sign Out
  </button>
);

export default SignOutButton;

I want to test that Firebase.auth().signOut is called onClick.

I have found this mock of Firebase.authelsewhere:

const authMock = jest.fn(() => {
  return {
    createUserAndRetrieveDataWithEmailAndPassword: jest.fn(() =>
      Promise.resolve(true)
    ),
    sendPasswordResetEmail: jest.fn(() => Promise.resolve(true)),
    signInAndRetrieveDataWithEmailAndPassword: jest.fn(() =>
      Promise.resolve(true)
    ),
    fetchSignInMethodsForEmail: jest.fn(() => Promise.resolve(true)),
    signOut: jest.fn(() => {
      Promise.resolve(true);
    }),
    onAuthStateChanged: jest.fn(),
    currentUser: {
      sendEmailVerification: jest.fn(() => Promise.resolve(true))
    }
  };
});

export { authMock };

In SignOutButton.test I have:

import React from "react";
import { render, cleanup, fireEvent } from "@testing-library/react";
import SignOutButton from "../.";
import Firebase from "../../../Firebase";
import { authMock } from "../../../../setupTests";
// @ts-ignore
Firebase.auth = authMock;

describe("<SignOutButton />", () => {
  afterEach(cleanup);

  it("calls Firebase signOut on click", async () => {
    const { getByText } = render(<SignOutButton />);
    const button = getByText("Sign Out");

    fireEvent.click(button);

    expect(Firebase.auth().signOut).toHaveBeenCalled();
  });
});

My test results in expected calls being 1 but receiving 0.

What am I doing wrong?

Thank you!

Upvotes: 3

Views: 5119

Answers (1)

Lin Du
Lin Du

Reputation: 102207

Your tests have the following issues:

  1. You should import SignOutButton component after replacing the Firebase.auth method with authMock. Otherwise, the Firebase.auth method is the original version rather than the mocked version. You can check using console.log(Firebase.auth).

  2. You should return the same reference for the returned value of Firebase.auth method. Otherwise, the assertions will fail.

The complete unit test solution:

SignOutButton.tsx:

import React from 'react';
import Firebase from './firebase';

console.log(Firebase.auth);
console.log('should keep same reference to authObject:', Firebase.auth() === Firebase.auth());

const SignOutButton = () => (
  <button type="button" onClick={() => Firebase.auth().signOut()}>
    Sign Out
  </button>
);

export default SignOutButton;

firebase.ts:

export default {
  auth() {
    console.log('auth real implementation');
    return this;
  },
  async signOut() {
    console.log('signOut real implementation');
  },
};

setupTests.ts:

const authObjectMock = {
  createUserAndRetrieveDataWithEmailAndPassword: jest.fn(() => Promise.resolve(true)),
  sendPasswordResetEmail: jest.fn(() => Promise.resolve(true)),
  signInAndRetrieveDataWithEmailAndPassword: jest.fn(() => Promise.resolve(true)),
  fetchSignInMethodsForEmail: jest.fn(() => Promise.resolve(true)),
  signOut: jest.fn(() => {
    Promise.resolve(true);
  }),
  onAuthStateChanged: jest.fn(),
  currentUser: {
    sendEmailVerification: jest.fn(() => Promise.resolve(true)),
  },
};
const authMock = jest.fn(() => authObjectMock);

export { authMock };

SignOutButton.test.tsx:

import React from 'react';
import { render, cleanup, fireEvent } from '@testing-library/react';
import Firebase from './firebase';
import { authMock } from './setupTests';
// @ts-ignore
Firebase.auth = authMock;

describe('<SignOutButton />', () => {
  afterEach(cleanup);

  it('calls Firebase signOut on click', async () => {
    const SignOutButton = (await import('./SignOutButton')).default;
    const { getByText } = render(<SignOutButton />);
    const button = getByText('Sign Out');
    fireEvent.click(button);
    expect(Firebase.auth().signOut).toHaveBeenCalled();
  });
});

unit test result with coverage report:

 PASS  src/stackoverflow/58562583/SignOutButton.test.tsx
  <SignOutButton />
    ✓ calls Firebase signOut on click (76ms)

  console.log src/stackoverflow/58562583/SignOutButton.tsx:382
    { [Function: mockConstructor]
      _isMockFunction: true,
      getMockImplementation: [Function],
      mock: [Getter/Setter],
      mockClear: [Function],
      mockReset: [Function],
      mockRestore: [Function],
      mockReturnValueOnce: [Function],
      mockResolvedValueOnce: [Function],
      mockRejectedValueOnce: [Function],
      mockReturnValue: [Function],
      mockResolvedValue: [Function],
      mockRejectedValue: [Function],
      mockImplementationOnce: [Function],
      mockImplementation: [Function],
      mockReturnThis: [Function],
      mockName: [Function],
      getMockName: [Function] }

  console.log src/stackoverflow/58562583/SignOutButton.tsx:386
    should keep same reference to authObject: true

-------------------|----------|----------|----------|----------|-------------------|
File               |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
-------------------|----------|----------|----------|----------|-------------------|
All files          |    63.64 |      100 |    36.36 |       60 |                   |
 SignOutButton.tsx |      100 |      100 |      100 |      100 |                   |
 firebase.ts       |       25 |      100 |        0 |       25 |             3,4,7 |
 setupTests.ts     |       50 |      100 |    28.57 |    44.44 |        2,3,4,5,11 |
-------------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        3.943s

Upvotes: 3

Related Questions