Unit testing for Firebase cloud functions with Jest

Haider Malik
4 min readNov 28, 2019

--

..because there’s hardly any resources on this

First off I want to thank all those Youtube tutorial makers who taught me more than my uni lecturers, they just read off slides.

Looking online there doesn’t seem to be much on automated testing for Firebase functions. So today I want to give back the knowledge I have acquired — especially now that I am ‘in between jobs’ and have the time.

Getting started

Step 1 — Install Jest:

  1. Head over to your console terminal and install the jest package with the following line:
> npm i jest

2. Then add this line in your in your package.json file

“test”: “jest — detectOpenHandles”

Step 2 — Add Firebase admin:

  1. Head over to your firebase console and download the config for your project
  2. Navigate to: Settings > Service Accounts > Generate new private key
  3. Add this file to your file firebase project (But be careful when adding this to your source control as it contains sensitive information)
  4. Keep this data as you will need it later

Step 3 — Setup test environment:

  1. Create the file index.test.js
  2. Add the following details, to setup test environment:
const test = require(‘firebase-functions-test’)({
databaseURL: ‘https://{project-name}.firebaseio.com',
storageBucket: ‘{project-name}.appspot.com’,
projectId: ‘{project-name}’,
},’./path/to/private-key.json’);

3. Add the admin SDK:

const admin = require(‘firebase-admin’);

You are now setup and ready to go :)

Create a basic test

Step 1 — Create a basic function

add the following in your index.js

exports.basicTest = function(){
const a = 1;
const b = 5;
return a + b;
}

Step 2 — Initialise the test case

First you will need to create a test case:

describe(‘testing basic function’,() =>{});

Step 3 — Setup test environment

Then you will need to setup the environment parameters with the beforeAll() method:

let index,adminStub;beforeAll(() =>{
adminStub = jest.spyOn(admin, “initializeApp”);
index = require(‘./index’);
return;
});

Step 4 — Setup the tear down of the test environment

then remember to clean up the environment at the end of the test case with the afterAll() method:

afterAll(() =>{
adminStub.mockRestore();
testEnv.cleanup();
});

Step 5 — Create the unit test

Now to create our unit test:

it(‘test function returns 6’,() =>{
expect(index.basicTest()).toBe(6);
});

We are expecting the function to return 6

Step 6 — Run the test

Head over to your console terminal and run the following:

> npm run test

Congratulations :) you ran your first unit test

Now we can test more substantial functions…

Test HTTPS function

Lets assume we are testing the Hello world function found in the firebase documents:

exports.helloWorld = functions.https.onRequest((req, res) => {
res.status(200).send(‘Hello, World!’);
});

it has 2 parameters — a request object and a response callback object. We will need to create mocks of these in order test. The test will look like this:

it(‘firebase HTML function test’, (done) =>{    // A fake request object
const req = {};
// A fake response object, with a send
const res = {
send: (response) => {
//Run the test in response callback of the HTTPS function
expect(response).toBe(“Hello from Firebase!”);
//done() is to be triggered to finish the function
done();
}
};
index.helloWorld(req,res);
});

Test Callable function

A callable has two parameters — data and context.

Lets assume we are using the example callable found in the firebase documentation here — this function is to sanitise a string passed to it.

Your test case should look like this:

it('test firebase callable function', () =>{const data = {
text : 'not this shit again'
};
const context = {
auth: {
uid: 'string',
token: {
aud: "firebaseproject-id",
auth_time: Date.now(),
exp: Date.now() + 1000000000,
firebase: {
identities: {
user: {}
},
sign_in_provider: "password",
},
uid: "test-user"
}
}
};
const message = index.addMessage(data, context); expect(message.text).toBe('not this **** again');});

After retrieving the message we can see the swear word has been sanitized.

if you want to test that the message has been added where it needs to be call the following:

//get a snapshot of the message we added
const insertedMessageSnapshot = await admin.database().ref(‘/messages’).limit(1).once(‘child_added’);
//check that the text matches the sanitized message
expect(insertedMessageSnapshot.value().text).toMatch(‘not this **** again’);

Test Database trigger function

Database triggers have 4 methods — onCreate(),onUpdate(),onWrite(),onDelete()

Starting with onCreate() — Lets assume we are using the example found in the firebase documentation here

onCreate() test:

it('test case 101 - onCreate() test', async (done)=>{
//create a context object
const context = {
params : {
userId : 'test-user',
pushId : '-ae29bba87606'
}
};
//create the original text string for snapshot later
const original = 'original text';
//create a dummy snapshot
//makeDataSnapshot requires 2 parameters - the value we want to send and the path reference
const createdSnapshot = test.database.makeDataSnapshot(original,'/messages/-ae29bba87606/original');
//wrap the database function
const wrappedFunction = test.wrap(index.makeUppercase);
//trigger the database function
await wrappedFunction(createdSnapshot,context);
//get a snapshot of the uppercase message created
const snapshot = await admin.database().ref('/messages/-ae29bba87606/uppercase').once('value');
//capitalised text test
expect(snapshot.value()).toMatch('ORIGINAL TEXT');
done();
});

Testing other firebase database triggers

Lets assume this is our onUpdate() function:

exports.datebaseUpdateTrigger = functions.database.ref(‘users/{userId}/shifts/{shiftId}’).onUpdate(async (change) => {
//code in here

}

We need to pass just a change object. A change object is made of 2 snapshots. We can made this as so:

const beforeShift = {
date : '10/10/2019'
};
const afterShift = {
date : '11/10/2019'
};
const before = test.database.makeDataSnapshot(beforeShift,'users/test-user/shifts');
const after = test.database.makeDataSnapshot(afterShift,'users/test-user/shifts');
const change = test.makeChange(before,after);

onWrite() and onDelete() functions require the two parameters — change & context.

Change object for onDelete() :

const beforeShift = {
date : ‘10/10/2019’
};
const before = test.database.makeDataSnapshot(beforeShift,’users/test-user/shifts’);
const after = test.database.makeDataSnapshot();
const change = test.makeChange(before,after);

on delete has nothing for its after as there is no snapshot after it is removed.

Now you can run unit tests for your firebase functions :)

--

--