Test Firebase Cloud Functions with Jest Apply test-driven development to your Firebase Cloud Functions with Jest 995 words. By Jeff Delaney Created May 23, 2018 Last Updated May 23, 2018 Code Slack #pro #typescript #jest #cloud-functions Firebase Cloud Functions makes building a serverless backend easy and fun, but the proper way to write unit tests in this environment is not exactly clear. In this episode, I will show you how to setup a testing environment for your functions and use Jest to implement unit tests. Jest is my preferred testing framework for Cloud Functions, but the official docs use Mocha, Sinon, and Chai. Either approach works. Test Environment Setup There are two ways to test functions - offline and online. With offline testing, you will create mocks and stubs to simulate a Cloud Function’s arguments. With online testing you will make real requests to Firebase using a dedicated development project, so your tests will read/write live data. **Should I write offline or online tests?** It's a matter of personal preference, but I do most of my testing online with a dedicated Firebase project for development. Offline tests require a ton of stubs, which can be cumbersome to setup and are more susceptible to human error. Let’s initialize functions and create a couple test files. firebase init functions # select TypeScript cd functions mkdir test touch test/offline.test.ts touch test/online.test.ts Install Jest Typescript Jest requires some setup, but by comparison to other test frameworks it is very minimal. Ideally, Jest is the only package you need to install to handle all Cloud Function testing. Install Jest TypeScript (and Jest globally) npm install --global jest npm install --save-dev jest ts-jest @types/jest Then update the package.json { "jest": { "transform": { "^.+\\.tsx?$": "ts-jest" }, "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", "moduleFileExtensions": [ "ts", "tsx", "js", "jsx", "json", "node" ] } } Now you’re ready to start testing. Use the watchAll flag to run tests in the background. Optionally add coverage to show code coverage. jest --watchAll --coverage Offline Testing Setup Offline tests… run faster require more code to setup mocks/stubs do not make any real changes to your data // functions/test/offline.test.ts import 'jest'; import * as admin from 'firebase-admin'; import * as functions from 'firebase-functions-test'; const testEnv = functions(); Online Testing Setup Online tests… run slower are easier to write (fewer stubs needed) should use a project isolated from your production app To setup online tests, you need to download your service account from the Firebase console and save the credentials to a file named /functions/service-account.json Do not expose the service account publicly - it allows full write access to your Firebase project. // functions/test/online.test.ts import 'jest'; import * as functions from 'firebase-functions-test'; import * as admin from 'firebase-admin'; // Online Testing const testEnv = functions({ databaseURL: 'https://xyz.firebaseio.com', storageBucket: 'xyz.appspot.com', projectId: 'xyz', }, './service-account.json'); Firestore Function Tests (Online) Cloud Function Our first cloud function runs when a Firestore document is created and simply converts a property on the document to lowercase. import * as functions from 'firebase-functions'; import * as admin from 'firebase-admin'; admin.initializeApp(); // Firestore Function export const lowercaseBio = functions.firestore .document('animals/{animalId}') .onCreate((snap, context) => { const data = snap.data(); const bio = data.bio.toLowerCase(); return admin.firestore().doc(`animals/${snap.id}`).update({ bio }); }); Spec Let’s test our function with live data to ensure that data gets updated in the database properly. Notice how we need to use testEnv.wrap(lowercaseBio) to make the function callable in our tests. This gives us a function that returns a Promise, therefore we can await the results in the spec. When the promise resolves, the database should be updated with the lowercase value. import { lowercaseBio } from '../src'; describe('downcaseBio', () => { let wrapped; // Applies only to tests in this describe block beforeAll(() => { wrapped = testEnv.wrap(lowercaseBio); }); test('it converts the bio to lowercase', async () => { const path = 'animals/meerkat'; const data = { bio: 'SUPER COOL' }; // Create a Firestore snapshot const snap = testEnv.firestore.makeDocumentSnapshot(data, path); // Execute the function await wrapped(snap); const after = await admin.firestore().doc(path).get(); expect(after.data().bio).toBe('super cool'); }); }); Auth Function Tests (Online) Cloud Function The auth function runs after a new user signup and will create a Firestore document under the UID to give them an initial ranking of noob. export const createUserRecord = functions.auth .user() .onCreate((user, context) => { return admin .firestore() .doc(`users/${user.uid}`) .set({ ranking: 'noob' }); }); Spec The auth function is tested in the same manner as Firestore. In fact, all background functions (Firestore, RTDB, Auth, Storage, PubSub) follow similar testing patterns. Notice how we use testEnv.auth.makeUserRecord(...). This allows us to generate a fake user record specifically for this test with the values we specify. describe('createUserRecord', () => { let wrapped; // Applies only to tests in this describe block beforeAll(() => { wrapped = testEnv.wrap(createUserRecord); }); afterAll( () => { admin.firestore().doc(`users/dummyUser`).delete(); testEnv.cleanup(); }); test('it creates a user record in Firestore', async () => { const user = testEnv.auth.makeUserRecord({ uid: 'dummyUser' }) await wrapped(user); const doc = await admin.firestore().doc(`users/${user.uid}`).get(); expect(doc.data().ranking).toBe('noob'); }); }); HTTP Function Tests (Offline) Cloud Function The HTTP function simulates how a credit card payment request might work. If the request is missing a credit card in the body then we send back an error. export const makePayment = functions.https.onRequest((req, res) => { if (!req.body.card) { res.send('Missing card!'); } else { res.send('Payment processed!'); } }); Spec Let’s make sure this behavior works property in offline mode. The key takeaway on this snippet is the stubbed request/response. Notice how we put the expectation inside the send method of the response, then call makePayment with our stubs. import { makePayment } from '../src'; /// HTTP describe('makePayment', () => { test('it returns a successful response with a valid card', () => { const req = { body: { card: '4242424242424242' } }; const res = { send: (payload) => { expect(payload).toBe('Payment processed!'); }, }; makePayment(req as any, res as any); }); }); The End There are many different possibilities when it comes to testing Cloud Functions. Ultimately, the right strategy depends on the critical business needs of the app, but live testing with Jest is a reliable approach for many situations. Let me know if you want to see any other testing strategies in the comments. Firebase Cloud Functions makes building a serverless backend easy and fun, but the proper way to write unit tests in this environment is not exactly clear. In this episode, I will show you how to setup a testing environment for your functions and use Jest to implement unit tests. Jest is my preferred testing framework for Cloud Functions, but the official docs use Mocha, Sinon, and Chai. Either approach works. Test Environment Setup There are two ways to test functions - offline and online. With offline testing, you will create mocks and stubs to simulate a Cloud Function’s arguments. With online testing you will make real requests to Firebase using a dedicated development project, so your tests will read/write live data. **Should I write offline or online tests?** It's a matter of … Upgrade to PRO for the Full Lesson Monthly $25/month Unlimited access to PRO courses Cancel anytime Slack #pro-member invite AngularFirebase Survival Guide Book No ads Quarterly $50/3mo All monthly tier benefits 💰 33.33% discount 🔥 Free Sticker Lifetime $300/one-time All monthly tier Benefits 💸 One-time payment 👕 Free Commemorative T-Shirt Login with Google ---- OR ---- You must be logged in to view this content. Login