Test Firebase Cloud Functions Access Firestore Emulator (Node.js)

April 15, 2020

Setup Firebase Hosting and Functions (Node)

You want to install Firestore Emulator as well

firebase init

     ######## #### ########  ######## ########     ###     ######  ########
     ##        ##  ##     ## ##       ##     ##  ##   ##  ##       ##
     ######    ##  ########  ######   ########  #########  ######  ######
     ##        ##  ##    ##  ##       ##     ## ##     ##       ## ##
     ##       #### ##     ## ######## ########  ##     ##  ######  ########

You're about to initialize a Firebase project in this directory:

  /code/firebase/journey

Before we get started, keep in mind:

  * You are currently outside your home directory
  * You are initializing in an existing Firebase project directory

? Which Firebase CLI features do you want to set up for this folder? Press Space
 to select features, then Enter to confirm your choices. Emulators: Set up local
 emulators for Firebase features

=== Project Setup

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add,
but for now we'll just set up a default project.

i  .firebaserc already has a default project, using journeyx.

=== Emulators Setup
? Which Firebase emulators do you want to set up? Press Space to select emulator
s, then Enter to confirm your choices. Functions, Firestore, Hosting
i  Port for functions already configured: 5001
? Which port do you want to use for the firestore emulator? 8080
i  Port for hosting already configured: 5000
? Would you like to download the emulators now? Yes
i  firestore: downloading cloud-firestore-emulator-v1.11.2.jar...
Progress: ======================================================> (100% of 90MB)

i  Writing configuration info to firebase.json...
i  Writing project information to .firebaserc...

✔  Firebase initialization complete!

Start Firestore Emulator

firebase emulators:start --only firestore,functions

NOTE: firebase serve --only functions,firestore used to be supported, but now it shows Error: Please use firebase emulators:start to start the Realtime Database or Cloud Firestore emulators. firebase serve only supports Hosting and Cloud Functions.

NOTE: I tried running firebase emulators:start --only firestore followed by firebase serve, it complained of The Cloud Firestore emulator is not running, so calls to Firestore will affect production. Despite the warning, the testing seems to work, though I am not aware of any side effects.

Install Packages

npm install firebase-admin --save
npm istall @firebase/testing --save-dev

Create cloud functions to access Firestore Emulator.

Edit functions/index.js.

Option 1: using @firebase/testing

const isDev = process.env.FUNCTIONS_EMULATOR

const functions = require('firebase-functions');

let admin       // server
let firebase    // testing
let firestore
let db

const projectId = 'PROJECT_NAME'

if (isDev) {
  firebase = require("@firebase/testing")
  db = firebase.initializeTestApp({ projectId: projectId }).firestore()
  firestore = firebase.firestore
  // admin = firebase
}
else {
  admin = require('firebase-admin')
  admin.initializeApp(functions.config().firebase)
  db = admin.firestore()
  firestore = admin.firestore
  // firebase = admin
}


// exports.requestInvite = functions.https.onRequest(async (req, res) => {
exports.requestInvite = functions.https.onCall(async (data, context) => {
  const email = data.email

  // use email as docId
  const docId = Buffer.from(email).toString('base64')
  const docRef = db.collection('request_invite').doc(docId)


  if (isDev) {
    // optional - delete database
    await firebase.clearFirestoreData({ projectId })
  }

  const doc = await docRef.get()
  if (doc.exists) {
    console.log('doc exist', doc.data())
  }

  await docRef.set({
    created: firestore.Timestamp.now(),
    email: email
  })

  return {
    text: `Hello ${email}!`,
    docId: docRef.id
  }
})

NOTE: Refer to onRequest vs onCall.

NOTE: Bear in mind @firebase/testing is a client side package, so data type such as firestore.Timestamp cannot be mixed with firebase-admin which is a server package.

Option 2: firebase-admin only

const functions = require('firebase-functions');

const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase)
const db = admin.firestore()
const firestore = admin.firestore

exports.requestInvite = functions.https.onCall(async (data, context) => {
  const email = data.email

  // use email as docId
  const docId = Buffer.from(email).toString('base64')
  const docRef = db.collection('request_invite').doc(docId)

  const doc = await docRef.get()
  if (doc.exists) {
    console.log('doc exist', doc.data())
  }

  await docRef.set({
    created: created: firestore.Timestamp.now(),
    email: email
  })

  return {
    text: `Hello ${email}!`,
    docId: docRef.id
  }
})

If you have Cloud Functions that use the Firebase Admin SDK to write to Cloud Firestore, these writes will be sent to the Cloud Firestore emulator if it is running. If further Cloud Functions are triggered by those writes, they will be run in the Cloud Functions emulator. - Source

NOTE: Once emulator is stopped, all data are automatically cleared.

References:

This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.