Firebase callable functions tests with emulator suite

Benjamin Petetot - Dec 17 '19 - - Dev Community

The Firebase emulator suite brings lot of new capabilities to test your Firebase code. In this article, I will experiment testing callable functions with jest and the Firestore emulator.

Here is a short callable function incrementing a counter document:

// increment.js
const functions = require('firebase-functions')
const admin = require('firebase-admin')

async function increment({ counterId, value }) {
  // get the counter document
  const ref = await admin
    .firestore()
    .collection('counters')
    .doc(counterId)
    .get()

  const counter = await ref.data()

  // increment and save the new counter value
  await admin
    .firestore()
    .collection('counters')
    .doc(counterId)
    .update({ value: counter.value + value })
}

module.exports = {
  increment: functions.https.onCall(increment),
}
Enter fullscreen mode Exit fullscreen mode

To test the function with jest and the emulator we will need to:

  • Execute jest and the emulator
  • Mock firebase-functions and firebase-admin
  • Write the test

Execute the emulator with jest

Following the Firebase emulator documentation, you need to install the emulator with this command:

firebase setup:emulators:firestore
Enter fullscreen mode Exit fullscreen mode

Then, execute the emulator and the jest test suite:

firebase emulators:exec --only firestore "jest"
Enter fullscreen mode Exit fullscreen mode

The emulator will start, then run the test suite, finally shut down the emulator after the tests run. You can add it as a test script in your package.json file. If you want to run jest in watch mode, just set "jest --watch" in the previous command.

Mock firebase-functions

For the test, we will directly execute the function, without using firebase-functions. So let's create a simple mock to retrieve exported callable function. Add a file firebase-functions.js in a __mocks__ folder:

// __mocks__/firebase-functions.js
module.exports = {
  https: { onCall: func => func },
}
Enter fullscreen mode Exit fullscreen mode

The mock will directly return the function given to functions.https.onCall, so we will able to execute it directly within the tests.

Mock firebase-admin

firebase-admin is used to get the Firebase app instance. We will replace it by the @firebase/testing app to be able to use the emulator. Add a file the firebase-admin.js in the __mocks__ folder:

// __mocks__/firebase-admin.js
const firebase = require('@firebase/testing')

module.exports = firebase.initializeAdminApp({ projectId: "projectId" })
Enter fullscreen mode Exit fullscreen mode

Now we are ready to write tests and we will be able to use Firestore emulator to store, retrieve and test data.

Write the tests

Thanks to the mocks, the emulator and @firebase/testing, you can:

  • Directly execute your function.
  • Create and retrieve documents in your test.
  • Clear firestore database before each tests to get isolated tests.
// increment.spec.js
const firebase = require('@firebase/testing')
const admin = require('firebase-admin')

// use mocks
jest.mock('firebase-admin')
jest.mock('firebase-functions')

const { increment } = require('./increment')

describe('Increment function', () => {
  afterAll(async () => {
    // close connexions to emulator
    await Promise.all(firebase.apps().map(app => app.delete()))
  })

  beforeEach(async () => {
    // clear firestore data before each tests
    await firebase.clearFirestoreData({ projectId: 'projectId' })
  })

  it('Should be able to increment the given counter', async () => {
    // create a counter document
    const counterId = 'counter1'
    await admin
      .firestore()
      .collection('counters')
      .doc(counterId)
      .set({ value: 10 })

    // call the 'increment' function
    await increment({ counterId, value: 20 })

    // get the counter to test the incremented value
    const updatedCounter = await admin
      .firestore()
      .collection('counters')
      .doc(counterId)
      .get()

    // check if we correctly get the counter document
    await firebase.assertSucceeds(updatedCounter)

    // check the counter value
    const { value } = await updatedCounter.data()
    expect(value).toBe(30)
  })
})
Enter fullscreen mode Exit fullscreen mode

Here you can now test your cloud functions locally and in isolation using Firestore emulator. 🎉

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .