Unit Testing Cloud Functions for Firebase: What's the "Right Way" to Test / Mock "Transactions" with sinon.js

Man, this bomb block test really kicks my ass.

I went through the documentation and read the examples they provide and got some of my more basic Firebase functions but I keep running into problems where I'm not sure how to check that a function transactionUpdated

passed along with refs .transaction

is updating the object correctly current

.

My struggle is probably best illustrated by their child-count

code example
and the bad attempt I made while writing a unit test for it.

Let's say my function that I want to unit test does the following (taken straight from this link):

// count.js
exports.countlikechange = functions.database.ref('/posts/{postid}/likes/{likeid}').onWrite(event => {
      const collectionRef = event.data.ref.parent;
      const countRef = collectionRef.parent.child('likes_count');

      // ANNOTATION: I want to verify the `current` value is incremented
      return countRef.transaction(current => {
        if (event.data.exists() && !event.data.previous.exists()) {
          return (current || 0) + 1;
        }
        else if (!event.data.exists() && event.data.previous.exists()) {
          return (current || 0) - 1;
        }
      }).then(() => {
        console.log('Counter updated.');
      });
    });

      

Unit Test Code:

const chai = require('chai');
const chaiAsPromised = require("chai-as-promised");
chai.use(chaiAsPromised);
const assert = chai.assert;
const sinon = require('sinon');

describe('Cloud Functions', () => {
  let myFunctions, functions;

  before(() => {
    functions = require('firebase-functions');
    myFunctions = require('../count.js');
  });

  describe('countlikechange', () => {
    it('should increase /posts/{postid}/likes/likes_count', () => {
      const event = {
        // DeltaSnapshot(app: firebase.app.App, adminApp: firebase.app.App, data: any, delta: any, path?: string);
        data: new functions.database.DeltaSnapshot(null, null, null, true)
      }

      const startingValue = 11
      const expectedValue = 12

      // Below code is misunderstood piece.  How do I pass along `startingValue` to the callback param of transaction
      // in the `countlikechange` function, and spy on the return value to assert that it is equal to `expectedValue`?
      // `yield` is almost definitely not the right thing to do, but I'm not quite sure where to go.
      // How can I go about "spying" on the result of a stub,
      // since the stub replaces the original function?
      // I suspect that `sinon.spy()` has something to do with the answer, but when I try to pass along `sinon.spy()` as the yields arg, i get errors and the `spy.firstCall` is always null. 
      const transactionStub = sinon.stub().yields(startingValue).returns(Promise.resolve(true))

      const childStub = sinon.stub().withArgs('likes_count').returns({
        transaction: transactionStub
      })
      const refStub = sinon.stub().returns({ parent: { child: childStub }})

      Object.defineProperty(event.data, 'ref', { get: refStub })

      assert.eventually.equals(myFunctions.countlikechange(event), true)
    })
  })
})

      

I have laid out the source code with my question, but I will repeat it here.

How can I verify that the transactionUpdate

callback
passed to the transaction stub will accept mine startingValue

and change it to expectedValue

, and then let me notice this change and assert that it happened.

This is probably a very simple problem with an obvious solution, but I'm very new to testing JS code where things need to be sharpened, so it's a bit of a learning curve ... Any help is appreciated.

+3


source to share


1 answer


I agree that unit testing in the Firebase ecosystem is not as easy as we would like. The team knows about this and we are working to make everything better! Fortunately, there are some good ways for you right now!

I suggest taking a look at this cloud features demo we just posted. We are using TypeScript in this example, but it will all work in JavaScript as well.

In the directory, src

you will notice that we have split the logic into three files: index.ts

has login logic, saythat.ts

has main business logic, and db.ts

is a subtle abstraction around the Firebase Realtime database. We test only saythat.ts

; we have intentionally kept index.ts

and db.ts

very simple.



In the catalog spec

we have unit tests; take a look at index.spec.ts

. The trick you are looking for: we use mock-require

to shred the entire file src/db.ts

and replace it with spec/fake-db.ts

. Instead of writing to a real database, we now store our completed operations in memory, where our unit test can check that they look correct. A concrete example is our field score

, which is updated in a transaction . Under mocking our database, our unit test is one line of code to make sure it's done correctly .

I hope this helps you test!

+6


source







All Articles