40

I can't seem to find the solution for this in the Firebase Documentation.

I want to test my functions.https.onCall functions locally. Is it possible using the shell or somehow connect my client (firebase SDK enabled) to the local server?

I want to avoid having to deploy every time just to test a change to my onCall functions.


My code

Function :

exports.myFunction = functions.https.onCall((data, context) => {
  // Do something
});

Client:

const message = { message: 'Hello.' };

firebase.functions().httpsCallable('myFunction')(message)
  .then(result => {
    // Do something //
  })
  .catch(error => {
    // Error handler //
  });
Community
  • 1
  • 1
czphilip
  • 897
  • 1
  • 8
  • 18

7 Answers7

61

For locally you must call (after firebase.initializeApp)

firebase.functions().useFunctionsEmulator('http://localhost:5000') 
  • 1
    Awesome! I will try this out when I have access to the code. BTW is this a newly added feature? – czphilip Jan 17 '19 at 03:46
  • 3
    This works! I was about to enter a never ending cycle of deploy-edit-deploy... This should be in the documentation guides. Thanks. – cbdeveloper Mar 12 '19 at 12:02
  • This works just make sure you are hitting the right port it may not always be :5000, - thank you! – Eli Himself Sep 30 '19 at 19:01
  • 3
    This is not working in my case. No method for functions().useFunctionsEmulator. Any help? – Darari Nur Amali Feb 15 '20 at 00:07
  • My imports: import firebase from 'firebase/app'; import 'firebase/functions' // important – Petr Pololáník Feb 16 '20 at 18:42
  • Note that `useFunctionsEmulator` is deprecated; use [useEmulator](https://firebase.google.com/docs/functions/local-emulator#instrument-functions) instead: `firebase.functions().useEmulator('127.0.0.1', 5001);` – thdoan Jun 11 '23 at 08:16
  • This method is now deprecated. https://firebase.google.com/docs/reference/android/com/google/firebase/functions/FirebaseFunctions Use useEmulator instead – Rodrigo Borba Jul 21 '23 at 00:13
8

There is a simple trick, how you can simplify onCall -function testing. Just declare the onCall function callback as a local function and test that instead:

export const _myFunction = (data, context) => { // <= call this on your unit tests
  // Do something
}

exports.myFunction = functions.https.onCall(_myFunction);

Now you can variate all cases with a normal function with the input you define on your function call.

Ville Venäläinen
  • 2,444
  • 1
  • 15
  • 11
  • Thanks a lot for this idea. Honestly, I feel the testing ergonomics of Firebase callables is abhorrent... Why would I need to stub / mock so many elements of the Firebase internals just to produce a valid test? I just want to check values in and values out. Your solution has the shortcoming that one can't test for the wrapped result of the Firebase callable, but I'll take it. – alexcs Feb 26 '21 at 11:38
  • To go further, you can use the Firebase's emulator. I have also been developing ts-mock-firebase -library for these purposes. Currently project have been inactive, mainly because the progress that Google has done with its own emulators. – Ville Venäläinen Mar 11 '21 at 06:41
  • Can I just comment that the onCall in the answer here should be onRequest. Thanks for the trick, it seems to work. What a mission – Wesley Barnes Mar 13 '21 at 17:11
7

Although the official Firebase Cloud Function docs have not yet been updated, you can now use firebase-functions-test with onCall functions.

You can see an example in their repository.

I have managed to test my TypeScript functions using jest, here is a brief example. There are some peculiarities here, like import order, so make sure to read the docs :-)

/* functions/src/test/index.test.js */
/* dependencies: Jest and jest-ts */

const admin = require("firebase-admin");
jest.mock("firebase-admin");
admin.initializeApp = jest.fn(); // stub the init (see docs)
const fft = require("firebase-functions-test")();

import * as funcs from "../index";

// myFunc is an https.onCall function
describe("test myFunc", () => {
  // helper function so I can easily test different context/auth scenarios
  const getContext = (uid = "test-uid", email_verified = true) => ({
    auth: {
      uid,
      token: {
        firebase: {
          email_verified
        }
      }
    }
  });
  const wrapped = fft.wrap(funcs.myFunc);

  test("returns data on success", async () => {
    const result = await wrapped(null, getContext());
    expect(result).toBeTruthy();
  });

  test("throws when no Auth context", async () => {
    await expect(wrapped(null, { auth: null })).rejects.toThrow(
      "No authentication context."
    );
  });
});
Brendan McGill
  • 6,130
  • 4
  • 21
  • 31
6

Callables are just HTTPS functions with a specific format. You can test just like a HTTPS function, except you have to write code to deliver it the protocol as defined in the documentation.

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
2

you should first check for dev environment and then point your functions to local emulator.
For JS:

//after firebase init
if (window.location.host.includes("localhost") ||
    window.location.host.includes("127.0.0.1")
) {
    firebase
      .app()
      .functions()  //add location here also if you're mentioning location while invoking function()
      .useFunctionsEmulator("http://localhost:5001");
  }

or if you don't create instance of firebase then

//after firebase init
if (window.location.host.includes("localhost") || 
   window.location.host.includes("127.0.0.1")
) {
    firebase
      .functions()
      .useFunctionsEmulator("http://localhost:5001");
  }

or when serving pages from backend (node.js):

//after firebase init
if (process.env.NODE_ENV === 'development') {
  firebase.functions().useFunctionsEmulator('http://localhost:5001');
}
GorvGoyl
  • 42,508
  • 29
  • 229
  • 225
0

if you are using angularfire, add this to you app.module

{
  provide: FirestoreSettingsToken,
  useValue: environment.production
    ? undefined
    : {
        host: "localhost:5002",
        ssl: false
      }
}
Ruben
  • 8,956
  • 14
  • 63
  • 102
0

There's a function attached to the HTTPSCallable function called run that:

"Executes the handler function with the provided data as input. Used for unit testing".

You can simply do this:

test("something", async () =>
{
  const res = await myCallableFunction.run({
    data: {
      // arguments
    },
    auth: { // optional
      uid: "test-user",
      token: { email: "test-email" } as any,
    },
    rawRequest: {} as any,
  });
});
Sergio Carneiro
  • 3,726
  • 4
  • 35
  • 51