firebase functions unit test emulator firebase-functions-test @firebase/testing typescript


  • @firebase/testing : use Emulator to run locally .
  • firebase-functions-test : use wrap to test functions in white-box style to get coverage.
  • jest.mock : let firebase functions in index.ts use @firebase/testing to connect with local Emulator.
  • DO NOT use functions Emulator! pls only use firestore Emulator to run! because we are testing functions in white-box style.
  • sh firebase emulators:start --only firestore
  • sh jest --coverage -i company.test
index.ts
import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';
import { DocumentSnapshot } from 'firebase-functions/lib/providers/firestore';

admin.initializeApp(functions.config().firebase)

console.log('[functions] Start functions')

export interface Company {
  name: string;
  nameInLowerCase?: string
}

export const createCompany = functions.firestore.document('/companies/{companyId}').onCreate(onCreateCompany);

async function onCreateCompany(snapshot: DocumentSnapshot, context: functions.EventContext) {
  const company = snapshot.data() as Company;
  const changes = {} as Company;
  const promises = [] as Promise<any>[];

  // Add createdAt timestamp
  //changes.createdAt = admin.firestore.Timestamp.fromDate(new Date(context.timestamp));

  // add lowercase name
  changes.nameInLowerCase = company.name.toLowerCase();

  // Update only changed properties
  promises.push(snapshot.ref.update(changes));

  // Increase companies count to aggregated company counts document
  promises.push(
    admin
      .firestore()
      .collection('counts')
      .doc('companies')
      .set({ totalCount: admin.firestore.FieldValue.increment(1) }, { merge: true })
  );

  // return promises array so that promises are resolved in parallel.
  return Promise.all(promises);
}
company.test.ts

import * as firebaseEmulator from '@firebase/testing';

const projectId = 'fir-test-d044e';
// initialize test database
process.env.GCLOUD_PROJECT = projectId;
process.env.FIRESTORE_EMULATOR_HOST = 'localhost:8080';
const db = firebaseEmulator.initializeAdminApp({
  projectId
}).firestore();

jest.mock('firebase-admin', ()=>{
  return {
    initialize: ()=>{},
    firebase: ()=>{
      return db;
    }
  }
});

// create testenv for mocking changes
const testEnv = require('firebase-functions-test')();

import { Company, createCompany } from '../../functions/src/index';

let wrapped: any;

// Create documentReference with created test database
const companyRef = db.collection('companies').doc('companyId1');

describe('Sample tests', () => {
  beforeAll(() => {
    // Creates wrapped test function from cloud function which can be called in tests
    wrapped = testEnv.wrap(createCompany);
  });

  afterAll(async () => {
    await Promise.all(firebaseEmulator.apps().map(app => app.delete()))
  });


  test('it should add lowercase name', async () => {
    const data = { name: 'Testers Inc.' };

    // write actual document to database
    await companyRef.set(data);

    // get document snapshot
    const afterSnap = await companyRef.get();

    // Execute the function
    await wrapped(afterSnap);

    const companySnapshot = await companyRef.get();
    const companyAfterCreate = companySnapshot.data() as Company;

    // Assert results
    expect(companyAfterCreate.nameInLowerCase).toBe('testers inc.');
  });

});

other

maybe https://www.npmjs.com/package/firebase-mock could be better.