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.