Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to setup a firebase firestore and cloud function test suit with firebase Emulator for JS development

According to the following google I/O (2019) post of the firebase team the new emulator allows us to combine firebase/database plus cloud function to fully simulate our firebase server codes. That should also mean we should be able to write tests for it.

we’re releasing a brand new Cloud Functions emulator that can also communicate with the Cloud Firestore emulator. So if you want to build a function that triggers upon a Firestore document update and writes data back to the database you can code and test that entire flow locally on your laptop (Source: Firebase Blog Entry)

I could find multiple resources looking/describing each individual simulation, but no all together

  1. Unit Testing Cloud Function
  2. Emulate Database writes
  3. Emulate Firestore writes
like image 874
Jürgen Brandstetter Avatar asked May 21 '19 10:05

Jürgen Brandstetter


2 Answers

For anyone struggling with testing firestore triggers, I've made an example repository that will hopefully help other people.

https://github.com/benwinding/example-jest-firestore-triggers

It uses jest and the local firebase emulator.

like image 134
Ben Winding Avatar answered Oct 29 '22 07:10

Ben Winding


To setup a test environment for cloud functions that allows you to simulate read/write and setup test data you have to do the following. Keep in mind, this really simulated/triggers cloud functions. So after you write into firestore, you need to wait a bit until the cloud function is done writing/processing, before you can read the assert the data.

An example repo with the code below can be found here: https://github.com/BrandiATMuhkuh/jaipuna-42-firebase-emulator .

Preconditions

I assume at this point you have a firebase project set up, with a functions folder and index.js in it. The tests will later be inside the functions/test folder. If you don't have project setup use firebase init to setup a project.

Install Dependencies

First add/install the following dependencies: mocha, @firebase/rules-unit-testing, firebase-functions-test, firebase-functions, firebase-admin, firebase-tools into the functions/package.json NOT the root folder.

cd "YOUR-LOCAL-EMULATOR"/functions (for example cd C:\Users\User\Documents\FirebaseLocal\functions)

npm install --save-dev mocha
npm install --save-dev firebase-functions-test
npm install --save-dev @firebase/rules-unit-testing
npm install firebase-admin
npm install firebase-tools

Replace all jaipuna-42-firebase-emulator names

It's very important that you use your own project-id. It must be the project-id of your own project and must exists. Fake ids won't work. So search for all jaipuna-42-firebase-emulator in the code below and replace it with your project-id.

index.js for an example cloud function

// functions/index.js

const functions = require("firebase-functions");
const admin = require("firebase-admin");

// init the database
admin.initializeApp(functions.config().firebase);
let fsDB = admin.firestore();

const heartOfGoldRef = admin
    .firestore()
    .collection("spaceShip")
    .doc("Heart-of-Gold");

exports.addCrewMemeber = functions.firestore.document("characters/{characterId}").onCreate(async (snap, context) => {
    console.log("characters", snap.id);

    // before doing anything we need to make sure no other cloud function worked on the assignment already
    // don't forget, cloud functions promise an "at least once" approache. So it could be multiple
    // cloud functions work on it. (FYI: this is called "idempotent")

    return fsDB.runTransaction(async t => {
        // Let's load the current character and the ship
        const [characterSnap, shipSnap] = await t.getAll(snap.ref, heartOfGoldRef);

        // Let's get the data
        const character = characterSnap.data();
        const ship = shipSnap.data();

        // set the crew members and count
        ship.crew = [...ship.crew, context.params.characterId];
        ship.crewCount = ship.crewCount + 1;

        // update character space status
        character.inSpace = true;

        // let's save to the DB
        await Promise.all([t.set(snap.ref, character), t.set(heartOfGoldRef, ship)]);
    });
});


mocha test file index.test.js

// functions/test/index.test.js


// START with: yarn firebase emulators:exec "yarn test --exit"
// important, project ID must be the same as we currently test

// At the top of test/index.test.js
require("firebase-functions-test")();

const assert = require("assert");
const firebase = require("@firebase/testing");

// must be the same as the project ID of the current firebase project.
// I belive this is mostly because the AUTH system still has to connect to firebase (googles servers)
const projectId = "jaipuna-42-firebase-emulator";
const admin = firebase.initializeAdminApp({ projectId });

beforeEach(async function() {
    this.timeout(0);
    await firebase.clearFirestoreData({ projectId });
});

async function snooz(time = 3000) {
    return new Promise(resolve => {
        setTimeout(e => {
            resolve();
        }, time);
    });
}

it("Add Crew Members", async function() {
    this.timeout(0);

    const heartOfGold = admin
        .firestore()
        .collection("spaceShip")
        .doc("Heart-of-Gold");

    const trillianRef = admin
        .firestore()
        .collection("characters")
        .doc("Trillian");

    // init crew members of the Heart of Gold
    await heartOfGold.set({
        crew: [],
        crewCount: 0,
    });

    // save the character Trillian to the DB
    const trillianData = { name: "Trillian", inSpace: false };
    await trillianRef.set(trillianData);

    // wait until the CF is done.
    await snooz();

    // check if the crew size has change
    const heart = await heartOfGold.get();
    const trillian = await trillianRef.get();

    console.log("heart", heart.data());
    console.log("trillian", trillian.data());

    // at this point the Heart of Gold has one crew member and trillian is in space
    assert.deepStrictEqual(heart.data().crewCount, 1, "Crew Members");
    assert.deepStrictEqual(trillian.data().inSpace, true, "In Space");
});


run the test

To run the tests and emulator in one go, we navigate into the functions folder and write yarn firebase emulators:exec "yarn test --exit". This command can also be used in your CI pipeline. Or you can use npm test instead.

If it all worked, you should see the following output

  √ Add Crew Members (5413ms)

  1 passing (8S)
like image 33
Jürgen Brandstetter Avatar answered Oct 29 '22 06:10

Jürgen Brandstetter