The wonderful world of serverless development brings us many gifts for a relatively low price, but as with everything there are also downsides. We are using Firebase, a serverless framework by Google, and we love it. However, let’s focus for now on a problem we encountered, and the tool we have created to make it work well: Firebridge.
One of the issues we face, with not being in full control of the database, is seeding and asserting the state of the database during development and testing. Manually preparing data every once in a while is doable for small projects, but when the projects get larger you need more tools. Since serverless is bleeding edge, the tool landscape is still being built. Let’s introduce a new tool to that landscape, a must-have in your project setup and during your (automated) tests: Firebridge.
yarn add @endran/firebridge
When using Firebase in large projects, you are faced with (at least) these two challenges
Any decent testing setup must be able to reliably control the data it is working with. This means we need to be able to reset Cloud Firestore with a consistent data set over and over again.
Frontend Admin Access:
We need to be able to insert data and assert data during end-to-end tests (for which we are using Cypress). We need to be able to bypass our elaborate security rules. For this we need Admin Access.
When naively adding the Firebase Admin SDK in your application, you will be faced with a world of hurt. The Firebase Admin SDK is meant to be used in a Node.JS environment, not in the browser, so it just won’t ever work.
The fastest way to get started with Firebase is to just start using it. The first couple of days you can do fine with manually creating data via the Firebase Console. You can grow your application together with the database, and everything is fine. But there will come a point where you will get afraid of touching the DB, since you cannot just roll back. This is where you must start to use Firebridge.
The creation process
Set up Firebridge, and run the FIRESTORE.EXPORT command. This will export the current state of your Firestore. You can commit this export file to Git.
Next, go ham on your package.json (or if you are not using NPM, choose your favourite build script tooling), and do something like below. See the README of Firebridge for the content of the command files.
"firebridge": "firebridge --serviceAccount firebase-admin.json",
"firestore:export": "yarn firebridge --commandPath 'commands/export.json'",
"firestore:clear": "yarn firebridge --commandPath 'commands/clear.json'",
"firestore:import": "yarn firebridge --commandPath 'commands/import.json'",
"firestore:reinit": "yarn firestore:clear && yarn firestore:import"
Now you are ready to experiment with your database once more. You can try new stuff in your app, and roll back whenever you want, or you can extend your data set with new functionality first, and then import it into Firestore. It also enables you to have several Firebase instances for various purposes and have a consistent database.
Next to testdata you often also need accounts. These can also be created using Firebridge. This way you can have a consistent set of users across various instances of Firestore for test purposes. The account details will also be in version control, so every developer can just look up the email and password fields of any test account. Even when new accounts are added, or existing accounts are changed, the entire team can just look it up. Again, see Firebridge for the details on the command files.
"firebridge": "firebridge -serviceAccount firebase-admin.json",
"auth:clear": "yarn firebridge --commandPath 'commands/clear-users.json'",
"auth:add": "yarn firebridge --commandPath 'commands/add-users.json'",
"auth:reinit": "yarn auth:clear && yarn auth:add"
End-to-end test way of working
Having a reliable data set to start with is a must for creating proper end-to-end tests, but it’s just a start. Often you need to create or mutate data, or you need to assert some value in the database at the end of a test run. It’s not always possible to read/write Firestore with the client SDK, due to security rules. And during setup or teardown of tests we’d rather use the Admin SDK, but this doesn’t run in the browser.
Recently we have fallen in love with Cypress, a new state-of-the-art framework for UI tests that offers – and I quote – “Fast, easy and reliable testing for anything that runs in a browser.”. It’s a blessing compared to Protractor. However, using the Firebase Admin SDK directly is a no go. You’ll get:
Oops...we found an error preparing this test file
Cypress always runs in a browser context, so we needed to overcome this.
Open the bridge
Open up Firebridge and choose your favourite port.
"firebridge": "firebridge -serviceAccount firebase-admin.json",
"openbridge": "yarn firebridge --open 3999"
In your test just call Firebridge with data you want to set, or data you want to get. See the README for the exact content of the POST body. Remember that whenever you set data via Firebridge, it will take a brief moment before you see it reflected in your application. That’s just life for serverless development, allow for the delay in your assertions (preferable not with sleeps).
The format of the body and the format of the command files is _exactly_ the same, so you could also do the command described earlier via HTTP as well. For example, we use AUTH.ADD and AUTH.DELETE to create/delete a fresh account, to test our login flow.
Internally Firebridge uses node-firestore-import-export, this library has support for complex data types like Timestamp and Geopoint. If Firebridge exported the content in plain strings, instead of this slightly more complicated format, then type information on types like Timestamp would be lost. Get familiar with the format, it’s worth it.
If you have Timestamp or Date in your test data, you run the risk of dates getting stale. For example, it could be that birthday information might be important for your application, so you need dates to be fresh. For this, use a tool like Testdate.
Firebridge enables you to have a consistent environment between multiple Firebase instances. You can create as many instances as you have developers, create a ci, acceptance and production environment, and configure them identically, in an instant.
Cover image by Luke Price.