Unit testing Firebase Firestore & Cloud Functions
Tags: firestore, cloudfunctions, unittest, testing
Published: 2019-09-30 09:49:11

---
title: Unit testing Firebase Firestore & Cloud Functions
published: true
description: Testing a Cloud Function that calls to Firestore
tags:
  - firestore
  - cloud-functions
  - unit-test
  - testing
cover_image: https://miro.medium.com/max/4096/1*AoeLTzsZ6i_31FYicOLdrA.png
---


A personal project of mine has me open the pandora's box of all fun and new technology. I usually am not able to use in my daytime job — Firebase Firestore and Cloud Functions (Lambdas for you AWS folk). 🤖

I challenged myself to write a function that takes a payload of data and creates a record in Firebase.  Along with this challenge, I wanted to wrap my functionality with unit-tests as a new stretch goal. 

The official [Firebase Cloud Functions documentation](https://firebase.google.com/docs/functions) is easy to read and understand for very basic use-cases. I wanted to go the extra mile beyond the primary examples. 😄

#Code

Here I have a simple function that listens to a Firestore document created event. It will invoke the Cloud Function to take the data, check if it exists and if not, create an associating record. 

```javascript
const functions = require('firebase-functions');
const admin = require('firebase-admin');

admin.initializeApp();
let db = admin.firestore();

exports.onEpisodeTrackCreated = functions.firestore.document('episodes/{episodeId}/tracks/{trackIndex}')
  .onCreate((snap, context) => {
    const data = snap.data()

    if (!data.name) throw new Error('Missing `name` parameter')

    const name = data.name.trim()
    let tracksRef = db.collection('tracks')

    return tracksRef.where('name', '==', name).get()
      .then(snapshot => {
        if (snapshot.empty) {
          return tracksRef.add({
            name: name
          })
        }
        let doc
        snapshot.forEach(snapDoc => {
          doc = snapDoc
        })
        return doc
      })
        .then((doc) => {
          snap.ref.set({
            trackId: doc.id
          }, { merge: true })
          return doc
        })
  })
```

#Test Setup

Install `firebase-functions-test` and Jest; a popular "batteries included" testing framework.

```bash
npm install --save-dev firebase-functions-test jest
```

We'll need to create a `test` folder where we will store the unit-tests for our functions. 

Next, I updated the `package.json` with the test script to call.

"scripts": {
  "test": "jest test/"
}

Firebase Cloud Functions can run in Online and Offline modes.  Online mode means it will interact with your Firebase account, create/destroy data.  Offline mode will result in us stubbing our calls, and this is the preferred option in my opinion for this writing. 

Initialize the SDK in offline mode by not defining any configuration options. 

```javascript
const test = require('firebase-functions-test')();
```

Let us continue with writing our unit test that invokes the function and should successfully resolve with the async/await otherwise it will throw an error.

```javascript
const test = require('firebase-functions-test')();
const functions = require('../index.js');

describe('onEpisodeTrackCreated', () => {
  it('successfully invokes function', async () => {
    const wrapped = test.wrap(functions.onEpisodeTrackCreated);
    const data = { name: 'hello - world', broadcastAt: new Date() }
    await wrapped({
      data: () => ({
        name: 'hello - world'
      }),
      ref:{
        set: jest.fn()
      }
    })
  })
})
```

What happens when we run the test now? 🤔


```bash

 FAIL  tests/index.test.js
  onEpisodeTrackCreated
    ✕ successfully invokes function (832ms)

  ● onEpisodeTrackCreated › successfully invokes function

    Could not load the default credentials. Browse to https://cloud.google.com/docs/authenti
cation/getting-started for more information.

      at GoogleAuth.getApplicationDefaultAsync (node_modules/google-auth-library/build/src/auth/googleauth.js:161:19)
      at GoogleAuth.getClient (node_modules/google-auth-library/build/src/auth/googleauth.js:503:17)
      at GrpcClient._getCredentials (node_modules/google-gax/src/grpc.ts:150:20)
      at GrpcClient.createStub (node_modules/google-gax/src/grpc.ts:295:19)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        1.91s
```

😢 this is not good. 

Thinking about this error a bit more, we do have quite a bit going on in our code.  This error is telling us something about the credentials. Perhaps it is to do with the `initializeApp` on the `firebase-admin`? 🤔

We'll mock that and see what happens next.

```javascript
jest.mock('firebase-admin', () => ({
  initializeApp: jest.fn()
}))
```

And the result...

```bash
 FAIL  tests/index.test.js
  ● Test suite failed to run

    TypeError: admin.firestore is not a function

      3 | 
      4 | admin.initializeApp();
    > 5 | let db = admin.firestore();
        |                ^
      6 | 
      7 | exports.onEpisodeTrackCreated = functions.firestore.document('episodes/{episodeId}/tracks/{trackIndex}')
      8 |   .onCreate((snap, context) => {

      at Object.firestore (index.js:5:16)
      at Object.require (tests/index.test.js:23:19)

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        1.757s
```

Brilliant, this is a better position to be in.  Because we're calling out to `firestore` but we've completely mocked the implementation this is as expected. 

Now to complete the mocking for this test. 😅

```javascript
const mockQueryResponse = jest.fn()
mockQueryResponse.mockResolvedValue([
  {
    id: 1
  }
])

jest.mock('firebase-admin', () => ({
  initializeApp: jest.fn(),
  firestore: () => ({
   collection: jest.fn(path => ({
     where: jest.fn(queryString => ({
       get: mockQueryResponse
     }))
   })) 
  })
}))

```

And the final run. 😬

```bash
 PASS  tests/index.test.js
  onEpisodeTrackCreated
    ✓ successfully invokes function (3ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.026s
```

Brilliant. 🙌


I really hope this solution helps you with testing your next project.


## Sources

Of course, this result did not come about organically, it took a great deal of searching through the internet for relevant solutions through the coding phase. 


- [Testing Firebase Cloud Functions with Jest by Gompro](https://medium.com/@leejh3224/testing-firebase-cloud-functions-with-jest-4156e65c7d29#0369)
- [Testing background (non-HTTP) functions by Google](https://firebase.google.com/docs/functions/unit-testing#testing_background_non-http_functions)
- [Async/Await by Jest](https://jestjs.io/docs/en/asynchronous.html#async-await)