11

Currently we are using 'firebase-functions-test' in online mode to test our firebase functions (as described here https://firebase.google.com/docs/functions/unit-testing), which we setup like so:

//setupTests.ts
import * as admin from 'firebase-admin';

const serviceAccount = require('./../test-service-account.json');

export const testEnv = require('firebase-functions-test')({
    projectId: 'projectId',
    credential: admin.credential.cert(serviceAccount),
    storageBucket: 'projectId.appspot.com'
});
const testConfig = {
    dropbox: {
        token: 'dropboxToken',
        working_dir: 'someFolder'
    }
};

testEnv.mockConfig(testConfig);

// ensure default firebase app exists:
try {
    admin.initializeApp();
} catch (e) {}

We would like to move away from testing against an actual firestore instance in our tests, and use the emulator instead.

The docs, issues, and examples I've been able to find on the net are either outdated, or describe how to set up the emulator for testing security rules, or the web frontend.

Attempts using firebase.initializeAdminApp({ projectId: "my-test-project" }); did not do the trick.

I also tried setting FIRESTORE_EMULATOR_HOST=[::1]:8080,127.0.0.1:8080

So the question is: How can I initialise the firebaseApp in my tests, so that my functions are wired up to the firestore emulator?

Bastian Stein
  • 2,147
  • 1
  • 13
  • 28
  • Strictly speaking, if a unit test accesses any external resource, even an emulator, it's no longer a unit test - it's an integration test. Typically you will want to mock any external dependencies so that the test is truly isolated (aka hermetic). If you want to perform integration tests using emulators, you would probably not want to combine those with unit tests, which are supposed to be hermetic. – Doug Stevenson Jul 24 '19 at 21:40
  • If you're interested in a full emulator experience (which would not involve unit testing or firebase-functions-test), you should look into the new emulator suite. https://firebase.google.com/docs/functions/local-emulator – Doug Stevenson Jul 24 '19 at 21:49
  • 2
    I edited the question to no longer refer to unit tests, as what you said is correct. The emulator which you linked is exactly what we are trying to get to work, hence the question on how to wire it up with our tests. – Bastian Stein Jul 25 '19 at 09:50
  • 1
    @BastianStein Did you ever figure out how to do this – melchoir55 Sep 17 '20 at 06:57

1 Answers1

11

I had another crack at it today, more than a year later, so some things have changed, which I can't all list out. Here is what worked for me:

1. Install and run the most recent version of firebase-tools and emulators:

$ npm i -g firebase-tools   // v9.2.0 as of now
$ firebase init emulators

# You will be asked which emulators you want to install. 
# For my purposes, I found the firestore and auth emulators to be sufficient

$ firebase -P <project-id> emulators:start --only firestore,auth

Take note of the ports at which your emulators are available:

console_output

2. Testsetup

The purpose of this file is to serve as a setup for tests which rely on emulators. This is where we let our app know where to find the emulators.

// setupFunctions.ts
import * as admin from 'firebase-admin';

// firebase automatically picks up on these environment variables:
process.env.FIRESTORE_EMULATOR_HOST = 'localhost:8080';
process.env.FIREBASE_AUTH_EMULATOR_HOST = 'localhost:9099';

admin.initializeApp({
    projectId: 'project-id',
    credential: admin.credential.applicationDefault()
});

export const testEnv = require('firebase-functions-test')();

3. Testing a simple function

For this, we setup a simple script which writes a document to firestore. In the test, we assert that the document exists within the emulator, only after we have run the function.

// myFunction.ts
import * as functions from 'firebase-functions';
import {firestore} from 'firebase-admin';

export const myFunction = functions
    .region('europe-west1')
    .runWith({timeoutSeconds: 540, memory: '2GB'})
    .https.onCall(async () => {
        await firestore()
            .collection('myCollection')
            .doc('someDoc')
            .set({hello: 'world'});

        return {result: 'success'};
    });
// myTest.ts
// import testEnv first, to ensure that emulators are wired up
import {testEnv} from './setupFunctions';
import {myFunction} from './myFunction';
import * as admin from 'firebase-admin';

// wrap the function
const testee = testEnv.wrap(myFunction);

describe('myFunction', () => {
    it('should add hello world doc', async () => {
        // ensure doc does not exist before test
        await admin
          .firestore()
          .doc('myCollection/someDoc')
          .delete()

        // run the function under test
        const result = await testee();

        // assertions
        expect(result).toEqual({result: 'success'});

        const doc = await admin
            .firestore()
            .doc('myCollection/someDoc')
            .get();
        expect(doc.data()).toEqual({hello: 'world'});
    });
});

And sure enough, after running the tests, I can observe that the data is present in the firestore emulator. Visit http://localhost:4000/firestore while the emulator is running to get this view.

screenshot of webui showing the created document

Bastian Stein
  • 2,147
  • 1
  • 13
  • 28