Firebase Emulation
When testing your web app, whether by hand or with automated testing code, you do not want the tests to use your production database. You certainly don't testing code, which might have bugs, to write data on the same database your users are using. You also probably want to control what's in the database to make sure it has specific data situations needed for a test, such as users with and without some preference setting.
Similarly, if automated tests need to authenticate in order to test parts of your app, you don't want to store user names and passwords -- real or fictional -- in test code.
With unit test frameworks, you can mock the code that calls the database, but an end-to-end tester like Cypress interacts with the app as a black-box. Cypress does provide a way to catch simple network calls to third-party APIs, such as Yelp or Spotify, with intercept(), but this does not work with services like Firebase that use network sockets to communicate.
For this reason, Firebase provides emulators that you can install and run. The emulators behave just like the real Firebase services but are run on your own machine. These emulators are Java programs so you need to have a Java runtime installed. There are emulators for all the major Firebase services. Similarly, Microsoft provides emulator for their Azure Cosmo DB, and Amazon provides an emulator for its LAMBDA cloud function service.
The key steps to using the Firebase emulators are:
- Install Java if not present
- Install the emulators
- Modify your code to use the emulators when testing
Install Java
The emulators are Java applications. To see if you have Java, run the following in a terminal window:
java -version
You need at least version 11 of Java. If you don't have it, you can get it from many places. Oracle has gone back and forth on how open their Java is. I recommend Adoptium.
Install emulators
You install the emulators you need with firebase init.
firebase init emulators
This will ask you which emulators you need. The most common options are:
- Authentication
- Realtime Database
- Firestore
- Cloud Storage -- needed if you used Cloud Storage to store pictures
You do not need the hosting emulator. Just use the normal local React development server to locally run your app.
Firebase will ask if you want to download the emulators. You can do this now, or have Firebase do this when the emulators are first run.
Test your setup by running
firebase emulators:start
This downloads and starts the emulators. It may take a minute. Firebase will display links for the web user interfaces for the emulated services you are running. Open those links to verify the emulators are working.
Troubleshooting Emulation
If you get a timeout opening the links, and you are using Node 17 or later, you probably need to replace all occurrences of localhost in the file firebase.json to 127.0.0.1. The final results should look like this:
{
"emulators": {
"auth": {
"port": 9099,
"host": "127.0.0.1"
},
"database": {
"port": 9000,
"host": "127.0.0.1"
},
"ui": {
"enabled": true
}
}
}
If you have other emulators installed, update their entries the same way.
If you get the message
Port 9000 is not open on localhost, could not start Database Emulator
that usually means a previous shutdown of the database emulator didn't kill all necessary processes. See this link for how to find and kill the previous process. Then try again.
If you get the message
Port 5000 is in use
that means you installed and are trying to run the hosting service. Remove the entry for hosting from ./firebase.json and try again.
If no specific reason is given, look in the debugging logs that Firebase creates
when the emulators fail to start. They will be in your app directory, usually in the
same directory that has firebase.json. Look for files with names like
firebase-debug.log and
database-debug.log.
Add emulator scripts
To make it easy to run the emulators for testing, add the following scripts to your package.json. You can give them any name you like. Here are some scripts for working with the Firebase emulators.
{
...
"scripts": {
...
"em:manage": "firebase emulators:start --import=./saved-data --export-on-exit",
"em:exec": "firebase emulators:exec --import=./saved-data 'npm start'",
"em:execui": "firebase emulators:exec --ui --import=./saved-data 'npm start'"
}
}
Use npm run em:manage to set up your local databases, including
test users and passwords. More below.
The option --import=./saved-data
will import data, saved in JSON format, from the directory ./saved-data.
The option --export-on-exit will cause any changes you make to the
data to be saved to the import directory when
you stop the emulators.
Use npm run em:exec or npm run em:execui
to start the emulators and then your app, for testing your app locally.
Changes made to data are NOT saved so that any effects from testing are forgotten.
The only difference is that execui
also starts the web console emulator so you can see what changes your
code is making to data.
Create test data
Start the emulators with
npm run em:manage
This will start the emulators and a
local Firebase console. Any existing static data in the folder
saved-data will be imported. When you finish, any
changes you made to the data will be saved.
- Open http://localhost:4000 in your browser. You should see a Firebase console.
- On the console page, click on the button for Authentication. Create whatever sample users you need for testing. Firebase will create local user IDs for them.
- On the console page, click on the button for the database you used, i.e., Realtime Database or Firestore. Create the test data you need for testing your app. For most testing, you need very little data. Just an example or two of each kind of data that you display or search for. If some data needs to be stored under a user ID, use the IDs created and displayed on the Authentication page.
- In your terminal shell where you started the emulators, stop them with control-C. This will trigger a graceful shutdown and save your data to the directory specified in the script.
Test! Do the above steps to create some relevant sample data for your app. Stop the emulator. Verify that files have been created in the directory saved-data in your repository. Those files are required for the steps belows. If you do not see saved data, check the log files in the repository that the emulators generate for error messages, such as firebase-debug.log, database-debug.log, and so on.
Avoid running two sets of emulators at once. This can lead to changes not being saved. For example, if you run em:manage, be sure to exit with control-C before running any tests that call em:exec.
For example, to create a test database for the Quick React Scheduler app:
- Start the database and authentication emulators.
- In the local authentication web console, create a test user, e.g., "Test User" with email address "tester@gmail.com"
- In the local realtime database web console, import the sample course data
- Edit the schedule title to "Test CS Courses" so that it's easy to know when test data is being used
- Stop the emulators with control-C to save the changes.