Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Race condition: Unit testing (Lazy functions) #38

Closed
mwalkerwells opened this issue Jan 10, 2019 · 4 comments
Closed

Race condition: Unit testing (Lazy functions) #38

mwalkerwells opened this issue Jan 10, 2019 · 4 comments
Assignees
Labels
wontfix This will not be worked on

Comments

@mwalkerwells
Copy link

mwalkerwells commented Jan 10, 2019

Problem

Since functions are lazily loaded, calling test.firestore.exampleDocumentSnapshot (and others) after stubbing initializeApp & importing your cloud functions file results in an error...

Error: The default Firebase app does not exist. Make sure you call initializeApp() before using any of the Firebase services.

Solution?

Add a test.setup() (pairs well with test.cleanUp()) function that calls src/app.ts#L42-L51 (attached below).

If I'm missing something, please let me know! Following the firestore documentation for unit tests led to some really unexpected behavior & was surprised to discover this (a lot of time)...

Another example here: https://stackoverflow.com/questions/49661782/firebase-functions-test-the-default-firebase-app-does-not-exist


Example Test (with workaround)

test.js

// IMPORTS
const chai = require('chai');
const assert = chai.assert;
const sinon = require('sinon');
const admin = require('firebase-admin');
const test = require('firebase-functions-test')();

// TESTS
describe('Cloud Functions', () => {
  let myFunctions, adminInitStub;

  before(() => {
    // ORDER REQUIRED
    // 1) Calling 'exampleDocumentSnapshot' only way to initialize testing app
    test.firestore.exampleDocumentSnapshot()

    // 2) Prevents multiple initializeApp calls
    adminInitStub = sinon.stub(admin, 'initializeApp');
    
    // 3) Import functions
    myFunctions = require('../');
  });

  after(() => { adminInitStub.restore(); test.cleanup(); });

  describe('Firestore Events', () => {

    it('', () => {
      const snapshotBefore  = test.firestore.exampleDocumentSnapshot();
      const snapshotAfter   = test.firestore.exampleDocumentSnapshot();
      const snapshotChange  = test.makeChange(snapshotBefore, snapshotAfter);
      const updateStub      = sinon.stub();
      const wrapped         = test.wrap(myFunctions. my_firestore_cloud_function);

      snapshotChange.after.ref.update = updateStub;
      updateStub.withArgs(snapshotChange.after.data()).returns(true);

      return assert.equal(wrapped(snapshotChange), true);
      
    })
  });

})

../index.js

// SIDE EFFECTS
const admin = require('firebase-admin');
admin.initializeApp();

// IMPORTS
// Firebase
const functions = require('firebase-functions');

// my_firestore_cloud_function :: CloudFunction Change DocumentSnapshot
const my_firestore_cloud_function = functions.firestore.document('/<collection>/<document>/').onWrite((change, context) => {
  // DO THINGS
});

Firebase Functions Test Library

firebase-functions-test/lib/providers/firestore.js

firestoreService = firestore(testApp().getApp());

https://github.com/firebase/firebase-functions-test/blob/master/src/providers/firestore.ts#L60

firebase-functions-test/lib/app.js

    getApp(): firebase.app.App {
      if (typeof this.appSingleton === 'undefined') {
        this.appSingleton = firebase.initializeApp(
          JSON.parse(process.env.FIREBASE_CONFIG),
          // Give this app a name so it does not conflict with apps that user initialized.
          'firebase-functions-test',
        );
      }
      return this.appSingleton;
    }

https://github.com/firebase/firebase-functions-test/blob/master/src/app.ts#L42-L51

@mwalkerwells
Copy link
Author

package.json

//...
    "firebase-admin": "^6.0.0",
    "firebase-functions": "^2.1.0",
    "@types/mocha": "^5.2.5",
    "chai": "^4.2.0",
    "firebase-functions-test": "^0.1.6",
//...

@silenceisgolden
Copy link

This is an excellent report and based off of the numerous number of reports for this issue, it would be in everyone's best interest if we could get an update on what the actual current best practice is. Right now it seems like no matter how you try to set up the test and the initializeApp() step, this error is what you get.

@laurenzlong laurenzlong self-assigned this Dec 14, 2019
@laurenzlong laurenzlong added the type: bug Something isn't working label Dec 14, 2019
@laurenzlong laurenzlong added wontfix This will not be worked on and removed type: bug Something isn't working labels Dec 27, 2019
@laurenzlong
Copy link
Contributor

Thanks for the report and I'm very sorry for the long silence on this! What you're trying to do is actually unsupported behavior, when you initialize the test SDK in offline mode you should be using stubbed data instead of calling exampleDocumentSnapshot or makeChange, see https://firebase.google.com/docs/functions/unit-testing#using_stubbed_data_for_offline_mode. When you have not initialize the test SDK in online mode, trying to do anything that requires a real Firebase app (which constructing a snapshot requires) leads to unexpected behavior. I will work with our tech writers to make this more clear in documentation.

@astyltsvig
Copy link

Thanks for the report and I'm very sorry for the long silence on this! What you're trying to do is actually unsupported behavior, when you initialize the test SDK in offline mode you should be using stubbed data instead of calling exampleDocumentSnapshot or makeChange, see https://firebase.google.com/docs/functions/unit-testing#using_stubbed_data_for_offline_mode. When you have not initialize the test SDK in online mode, trying to do anything that requires a real Firebase app (which constructing a snapshot requires) leads to unexpected behavior. I will work with our tech writers to make this more clear in documentation.

Hello!

I feel that what you are saying, and what their documentation states are in contradictory with each other.

image

The documentation says that we are perfectly able to use makeChange on a offline-mode testing?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
wontfix This will not be worked on
Projects
None yet
Development

No branches or pull requests

4 participants