如何用Jest测试Firebase(附代码示例)

244 阅读1分钟

每次我使用Firebase时,都会遇到如何测试Firebase的数据库和认证的问题。由于我使用Jest作为我的默认测试环境,我想我需要的一切都已经在Jest中出现了。在本教程中,你将学习如何模拟Firebase的功能。我们将使用Firebase Admin SDK来设置Firebase,然而,使用Firebase Real-Time Database、Firebase Firestore和Firebase Authentication的传统客户端Firebase也同样适用。

import * as firebaseAdmin from 'firebase-admin';

import firebaseServiceAccountKey from './firebaseServiceAccountKey.json';

if (!firebaseAdmin.apps.length) {
  firebaseAdmin.initializeApp({
    credential: firebaseAdmin.credential.cert(
      firebaseServiceAccountKey
    ),
    databaseURL: 'https://my-firebase-application.firebaseio.com',
  });
}

export default firebaseAdmin;

在设置好Firebase之后,我们有了第一个数据库函数,它在Firebase的数据库中创建了一条记录:

import firebaseAdmin from './firebase';

export const createCourse = async ({
  uid,
  courseId,
  bundleId,
  amount,
  paymentType,
}) => {
  await firebaseAdmin
    .database()
    .ref(`users/${uid}/courses`)
    .push()
    .set({
      courseId: courseId,
      packageId: bundleId,
      invoice: {
        createdAt: firebaseAdmin.database.ServerValue.TIMESTAMP,
        amount,
        licensesCount: 1,
        currency: 'USD',
        paymentType,
      },
    });

  return true;
}

在我们的测试文件中,用Jest做的测试可以类似于这个测试Firebase函数:

import { createCourse } from './';
import firebaseAdmin from './firebase';

describe('createFreeCourse', () => {
  it('creates a course', async () => {
    const set = firebaseAdmin
      .database()
      .ref()
      .push().set;

    const result = createCourse(
      '1',
      'THE_ROAD_TO_GRAPHQL',
      'STUDENT',
      0,
      'FREE'
    );

    await expect(result).resolves.toEqual(true);

    expect(set).toHaveBeenCalledTimes(1);

    expect(set).toHaveBeenCalledWith({
      courseId: 'THE_ROAD_TO_GRAPHQL',
      packageId: 'STUDENT',
      invoice: {
        createdAt: 'TIMESTAMP',
        amount: 0,
        licensesCount: 1,
        currency: 'USD',
        paymentType: 'FREE',
      },
    });
  });
});

在这个测试运行之前,我们需要在测试文件中模拟Firebase来覆盖有问题的行(突出显示)。我们不把Firebase当作库来模拟,而是模拟发生在另一个文件中的设置,我之前已经展示过。

import { createCourse } from './';
import firebaseAdmin from './firebase';

jest.mock('./firebase', () => {
  const set = jest.fn();

  return {
    database: jest.fn(() => ({
      ref: jest.fn(() => ({
        push: jest.fn(() => ({
          set,
        })),
      })),
    })),
  };
});

describe('createFreeCourse', () => {
  ...
});

现在可以调用Jest的toHaveBeenCalledTimes()toHaveBeenCalledWith() 在被模拟的函数上。然而,我们仍然没有正确地模拟Firebase的时间戳。在我们的源代码中,让我们使用明确的Firebase导入,而不是我们的Firebase设置的时间戳。

import * as firebaseAdminVanilla from 'firebase-admin';

import firebaseAdmin from './firebase';

export const createCourse = async ({
  uid,
  courseId,
  bundleId,
  amount,
  paymentType,
}) =>
  await firebaseAdmin
    .database()
    .ref(`users/${uid}/courses`)
    .push()
    .set({
      courseId: courseId,
      packageId: bundleId,
      invoice: {
        createdAt: firebaseAdminVanilla.database.ServerValue.TIMESTAMP,
        amount,
        licensesCount: 1,
        currency: 'USD',
        paymentType,
      },
    });

现在,我们可以在测试中用Jest来模拟Firebase常量的Firebase导入:

import { createCourse } from './';
import firebaseAdmin from './firebase';

jest.mock('firebase-admin', () => {
  return {
    database: {
      ServerValue: {
        TIMESTAMP: 'TIMESTAMP',
      },
    },
  };
});

jest.mock('./firebase', () => {
  ...
});

describe('createFreeCourse', () => {
  ...
});

Firebase常量现在在我们的测试断言中应该是正常的。你也可以考虑把最后一个Firebase模拟移到你的jest.setup.js 文件中,因为其他单元和集成测试也可能需要它。毕竟,你现在应该已经掌握了测试Firebase应用程序的一切。