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

Async test does not wait for await in https.onRequest triggered function #43

Closed
noelmansour opened this issue Jul 8, 2019 · 1 comment

Comments

@noelmansour
Copy link

Version info

firebase-functions-test: ^0.1.6

firebase-functions: ^3.0.2

firebase-admin: ^8.2.0

Test case

functions/test/index.test.ts:

import * as sinon from 'sinon';
import * as admin from "firebase-admin";
import * as chai from 'chai';

const test = require('firebase-functions-test')({
    projectId: 'project-id',
});

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

    before(() => {
        admin.initializeApp();
        functions = require('../src/debug');
    });

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

    afterEach(() => {
        sinon.restore();
    });

    describe('callBar', () => {
        it('should call bar without error', async () => {
            console.log('test: about to await');

            await test.wrap(functions.bar)({}, {});

            console.log('test: done await');
        })
    });

    describe('callFoo', () => {
        it('should call foo without error', async () => {
            const sendStub = sinon.stub();

            console.log('test: about to await');
            await functions.foo({}, {
                send: sendStub,
            });
            console.log('test: done await');

            chai.assert(sendStub.calledOnceWith(200));
        })
    });

});

functions/src/debug.ts:

import * as functions from 'firebase-functions';

export const foo = functions.https.onRequest(async (req, resp) => {
    await delayedPromise();
    resp.send(200);
});

export const bar = functions.https.onCall(async (data, context) => {
    await delayedPromise();
});

async function delayedPromise() {
    console.log('https: about to await');
    await new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve();
        }, 500);
    });
    console.log('https: done await');
}

Steps to reproduce

Run the tests. I'm using the npm run test script from my package.json:

{
  "scripts": {
    "test": "mocha --require ts-node/register test/**/*.ts"
  }
}

Expected behavior

The test should await until all awaits from the function are done (for all function trigger types).

Actual behavior

The test does not wait for the await call from the https.onRequest trigger. This is the output from running the tests.

For the https.onRequest triggered function, the https: done await appears after the test: done await (not expected). However, for the https.onCall triggered function, the https: done await log appears before the test: done await (expected).

  Cloud Functions
    callBar
test: about to await
https: about to await
https: done await
test: done await
      ✓ should call bar without error (504ms)
    callFoo
test: about to await
https: about to await
test: done await
      1) should call foo without error


  1 passing (785ms)
  1 failing

  1) Cloud Functions
       callFoo
         should call foo without error:
     AssertionError: Unspecified AssertionError
      at Object.<anonymous> (test/index.test.ts:45:18)
      at Generator.next (<anonymous>)
      at fulfilled (test/index.test.ts:4:58)
      at <anonymous>
      at process._tickCallback (internal/process/next_tick.js:189:7)



https: done await
@noelmansour noelmansour changed the title Test not waiting for await in https.onRequest triggered function Async test does not wait for await in https.onRequest triggered function Jul 8, 2019
@noelmansour
Copy link
Author

After some further investigation I've managed to find a workaround.

Looking at the function declarations for both:

export declare function onRequest(handler: (req: Request, resp: express.Response) => void): HttpsFunction;
export declare function onCall(handler: (data: any, context: CallableContext) => any | Promise<any>): HttpsFunction & Runnable<any>;

I can see that onCall returns a Promise whereas onRequest returns void. This would explain the behavior noted above.

In fact, if the onRequest implementation uses promise/then (no async/await) the same issue can be observed. i.e.

export const foo = functions.https.onRequest((req, resp) => {
    delayedPromise().then(() => resp.send(200));
});

Also, according to https://firebase.google.com/docs/functions/unit-testing#testing_http_functions, "Testing of HTTP Callable Functions is not yet supported" so using a https.onCall triggered function is not a very useful comparison.

One approach is to call done() in the Response.send() mocked implementation. Alternatively, supertest can be used as per https://cloud.google.com/functions/docs/bestpractices/testing#integration_tests_2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant