Skip to content

Firebase emulation

Created: 2020-03-26 15:31:11 -0700 Modified: 2020-06-18 16:47:45 -0700

Local emulators for Firebase features

Section titled Local emulators for Firebase features

After struggling with the emulators for a couple of days now (March, 2020), I’ve come to the conclusion that you can’t fully rely on local emulation. The feature is still in beta, so it may eventually improve. Here were the problems I ran into:

  • There’s no GUI for viewing any data that you store in Firestore, so it’s difficult to figure out whether you’re setting things correctly (reference).
  • Functions that are supposed to work on creating/deleting a user will not get triggered (reference).
  • Authentication is always done on Google’s servers, so your local Firestore may not reflect the users that are signed up in your project’s Authentication tab.

These can all be worked around. My major worry was that I would later run into more problems with the emulator. I’m still using them, just not for everything.

  • If you use “firebase init emulators” (reference), then you can have this install the emulators for you in a particular project.
  • I got an error saying The engine “node” is incompatible with this module. Expected version “8”. Got “10.15.0”
    • For this, I just modified the package.json to set the engine to “10” hoping that it’ll work. Part of the reason why I’m doing this is because I’m on Windows, and Windows doesn’t have a great version of NVM.
  • I also got an error due to my eslint config in the parent directory of “functions” not having eslint-plugin-promise
  • Before being able to point your site at any of the emulated services, you need to set up the web app first as their instructions mention (reference)
  • If you want to access any Firebase APIs or Google Cloud APIs from your emulated functions, you need to set up real service-account credentials (reference). Otherwise, authentication is sort of faked, so you can just provide a credential of admin.credential.applicationDefault().
    • Note: If, for whatever reason, you have to provide a JSON cert via admin.credential.cert() (which should never happen), then you can use a fake JSON blob I made with a fake private key that works just fine since all of the expected fields are there:
{
"type": "service_account",
"project_id": "test",
"private_key_id": "test",
"private_key": "-----BEGIN RSA PRIVATE KEY-----nMIIEpQIBAAKCAQEAuJ54e4t7ElJ5F59uxFimGvwDkjdrzuHhyrRdXa5fF7Z5Bi6HncvAOybqHY8k1qrBZvq41vQbzVxbWo9khLs0UfOoEy6ZR5RiwbAJFom7b8scbERCSnvsgKUV+fo6jbmgP7ncy+dZ0oZCPPHowT+ZxHjqA4vQBcGik7KgQ52RcKC2Qo3UM/nSoRqps58Km7aBYi3fPgtkjsqHXz75nxXzVSqB0o8k+vCfxiMU+HKodimn8TQMGljnBkx9nBnvOcwbtHo6jfW3xQtiA8bSQd5gMNcAnp5fzxTAv5MSTfMHttot2q2cYkt0nemFM4Dwc7cB/IWYAK+ICDyCag6I24cDki6PkgQIDAQABAoIBAQCIgRuidZItDO/enCJM0+DrxDs9xKCs9T4TMhSnXn5tOBCxd5r9du6Ojgpiirl5Q842X0GWJe38nMIrJnoTzIxvfUGJAtJ6+7zdJ8Of3UyVU1oAQiTPfDulLusd5ueubMbQ95MK7OdcIssNFuny7HlSKkcCtmI3aA786p8OZ2wJznVCxsy8fMU+146X2F8KW4n5ObtOWJgz4DgWHF7nIaEf4HQI60L04ORIe+bbHX0tcLDXepV93r/RSWnnb+NWzcYtuONn7XY09bIIrRggn6M7CSEidcgc+nIU42ih1fw3wgmJoOY7vf7qatLkjuHuWpf7GL1ZCfd4ujGPJUPnknjsGiWif1AoGBANmObJcQJ8nJ1ZZLAxR1EMfhR18JZmluMFeuDqnHMp/NbNWswtOwnRabL0E9bAeP5It+aayi13vExbvRyO3hsbbB/FaOOIkU7oRw8tbAHLEtvVi8LXw8Qni19R/Sfmrji1GXI6abhqEjhSn55JkBbpi8YAIsvy9e1AumsqkHeEdemDAoGBANk+nEGflln9kKBeP5VqTbTvF48+wwW3SN/Lhu7bRSfxFhd+RFY8hUpy3Ig2Js6HIxPCmn9f5BpmoA904GpNGwtfiG/xGLWY/VcwYjKWBuRE1M8ANTt0elXYqiXN1ekv45oMj1nTcRQQiCJvE1dpZ1A7D7MuxpXFzUBVBIJBRqfaU6rAoGBAIJIv5ELtLgsLcOOcm66n2GzK5WHtkC4NNdgcPV3BE/kNHWMszZPWGTAVGE+dkZlDpnW/1PKRT8yjLO4oxqKMnVHgA8yV6Rd2uIwJ0/N1IvmcCattLdQbhgoV35SITDDybf3yfrJYqt3SRTlONfEOsnYu8VP8FhY5NMUxdAazmX0sEfAoGBAInpmbarjNhAuCcFJeBTeWQZ497k4lcavoQFnhAqyYNqzNCLS0zHzQKPWqtqXUZ9ieowgIkiQWtou1NEG7LlmCo/E+8aihXAHXKdHnySu7tMayEii6i78DHxg0rOmn8X9NDGKnQABhxykq54zh55KmNZREmn9FTKlMt5dfnfIsDKnJTAoGAE06cjFu5sx7nmoOGgyBYUfTcLO1Nu++D0y4t1VvQSXXPP2hGqyT1n9KSzQmqy0pulJddhJKs4vZqeQhPK5uVZjCICpFyI1kRjnMHA1rQmVgmyL+/NcOPenSuB2QKUACM8EhjtmvNgwT8zvD4rfqJ5764m4pveUdnke5w6roHX1/Cs=n-----END RSA PRIVATE KEY-----n",
"client_email": "test@example.com",
"client_id": "12345",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/testeroni.iam.gserviceaccount.com"
}
  • Tell your application to talk to the emulators (reference)
firebase.functions().useFunctionsEmulator("http://localhost:5001")
const db = firebase.firestore();
db.settings({
host: "localhost:8080",
ssl: false
});
  • Note: in the admin SDK, you can just copy/paste the variable that the emulator gives you to connect to Firestore without having to modify any code whatsoever: set FIRESTORE_EMULATOR_HOST=localhost:5002
  • You can start multiple emulators in the same “hub” by starting them all at once: firebase emulators:start —only firestore,functions
  • Emulating hosting is apparently only for when you have a Cloud Function that generates dynamic content to be served by hosting, so you may not need it if you’re just statically hosting your own application.
    • The emulator doesn’t automatically reload rewrites from firebase.json; you need to restart the emulator for that.
  • ”console.log” from any Cloud Functions will print to the console that you started the emulator on
  • When a function has an error in it, you’ll see an error print in the emulator’s console, but if you fix the error, you won’t see a “success” message, but you can run your function anyway.
  • Every time you restart the emulator, you’ll have a brand new instance of the database.
  • To access onRequest functions, use the URL that the console spews. It looks like this: http://localhost:5001/your-project-name/us-central1/name-of-function
  • You can debug functions with “emulators:start —inspect-functions debug_port” (reference, reference2)

READ THIS: when I originally wrote this note on March 26th, 2020, I couldn’t get either route below working. However, as of April 20th, the GUI just magically started working when I ran the emulators with “firebase emulators:start —only firestore,functions,hosting”, so I don’t know which route below actually did it. I’m leaving the notes unaltered for now, but know that something below should explain how to get started. This thread may have more information.

There are tools available for this from the official Firebase repo:

  • Switch to some directory where you want the tools to live. This doesn’t have to be anywhere near your project.
  • Run these commands
    • git clone git@github.com:firebase /firebase-tools-ui.git
    • cd firebase-tools-ui
    • yarn
    • firebase emulators:exec —project sample —only database,firestore “npm start”
  • You can have the emulators import state for firestore with “—import” like they have in the example.

There’s also an experimental feature for launching a GUI (reference). It doesn’t work with Firestore yet. I had to manually download the zip file that they’re referencing, set environment variables (set GCLOUD_PROJECT=foo && set FIREBASE_EMULATOR_HUB=localhost:4400), then stop my own web server from taking port 3000 and run “node server.bundle.js”. Even then, I couldn’t really test anything since it doesn’t work with Firestore.

Differentiating between development and production

Section titled Differentiating between development and production

You actually have three environments if you think about it:

  • Development environment pointing at the emulator
  • Development environment pointing at production data
  • Production environment pointing at production data

When running with the emulator, you don’t even need a real credentials file (i.e. everything can be spoofed in it since no real auth is done).

Options for differentiating:

  • All services
    • You can make project aliases (reference). E.g. this lets you have staging and production environments in Firebase, then you can just “firebase use staging” if you want to swap between them.
      • This will count toward your billing since it’s running on Firebase itself and not an emulator.
  • Cloud Functions for Firebase
    • You can set environment variables for the functions to access, but the process differs between the emulator and production.
      • Emulator (reference): you write them as a JSON file in functions/.runtimeconfig.json, e.g.

{

“foo”: {

“bar”: “hello”

}

}

  • Production (reference): you set them via the CLI (firebase functions:config:set foo.bar=“baz” foo.qux=“cat”).

  • Either way, here are some notes:

    • All environment variables need two lowercase components, e.g. “a.b”.
    • Your function’s code reads them via “functions.config().foo.bar”.
  • Firestore

    • You may think you want to change your rules just for development, but I feel like that’s a bad idea. firebase.json has a “firestore” key that lets you change the “rules” file, but it’s used for both the emulator and for deploying to production (reference). Since there’s no official solution yet for changing firebase.json dynamically (see this issue), here are some workarounds:
      • Consider getting a pre-commit hook that will check the filename or some substring to make sure it’s not the development-specific copy.
      • Make two separate folders completely with two different firebase.json files (reference).
      • HiDeoo: Adam13531 You could just make a prod version, commit it and then permanently ignore changes to this file through git
        • [09:54] HiDeoo: Adam13531 Yeah, a mix of update-index —assume-unchanged, gitignore & cleaning the cache
    • However, my conclusion from all of this is that you shouldn’t have development and production unaligned in terms of permissions here. If you really need some administrative capabilities just for development, then you can make a Cloud Function for Firebase that only works in dev on data that people typically don’t have access to.

This isn’t possible for right now, meaning something like “functions.auth.user().onCreate” won’t ever get triggered locally. You have some workarounds:

  • Make your “functions.auth.user().onCreate” function on production Firebase and view any logs there through the dashboard.
  • Make any “automatic” functions be called by the client. E.g. there’s onCreate which would have gotten invoked on creating an account, so you could just call your own “onCreate” function from the client.
  • Make a completely separate project and then call “firebase use” to switch between your projects (reference).

Until this is officially made possible, you’ll get an error like this when you start the emulator and you have an onCreate functon:

functions[onAccountCreation]: function ignored because the auth emulator does not exist or is not running.

Auth error: Credential implementation provided to initializeApp() via the “credential” property failed to fetch a valid Google OAuth2 access token with the following error

Section titled Auth error: Credential implementation provided to initializeApp() via the “credential” property failed to fetch a valid Google OAuth2 access token with the following error

This likely means that you’re calling into a “real” API that requires authentication to Firebase. In this case, set up admin credentials by setting the GOOGLE_APPLICATION_CREDENTIALS environment variable.

If it’s still happening even after you set it, then perhaps your credentials don’t match the right project/account (e.g. dev vs. prod credentials).

Error: 14 UNAVAILABLE: No connection established

Section titled Error: 14 UNAVAILABLE: No connection established

This happened to me when I tried using the admin SDK to connect to the real Firebase (i.e. not an emulated one). It turns out, I had the FIRESTORE_EMULATOR_HOST environment variable set, so it was trying to connect to the local one even though it was offline.

The emulated functions’ region doesn’t match my app’s region

Section titled The emulated functions’ region doesn’t match my app’s region

As far as I can tell, the region is always us-central1, but it’s because it doesn’t actually matter (you could literally set it to “made-up-region” and it would work). I.e. this is not really a problem, so if you’re not able to access functions correctly, then something else is wrong, e.g. that the function URLs are wrong for a different reason like not even exporting the functions in the first place. When the emulator starts up, it shows the URLs of your functions: