首页 > 解决方案 > 如何模拟 Firebase Auth 方法?(反应,测试库)

问题描述

我有以下组件:

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

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

export default SignOutButton;

我想测试那个Firebase.auth().signOut叫做onClick.

我在Firebase.auth其他地方找到了这个模拟:

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

在 SignOutButton.test 我有:

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

我的测试结果是预期呼叫为 1,但接收为 0。

我究竟做错了什么?

谢谢!

标签: reactjsfirebasejestjsmockingreact-testing-library

解决方案


您的测试存在以下问题:

  1. 您应该SignOutButton在将Firebase.auth方法替换为authMock. 否则,该Firebase.auth方法是原始版本而不是模拟版本。您可以使用console.log(Firebase.auth).

  2. 您应该为方法的返回值返回相同的引用Firebase.auth。否则,断言将失败。

完整的单元测试解决方案:

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

带有覆盖率报告的单元测试结果:

 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

推荐阅读