Start the Emulators in Development
Learn how to start the emulators in development.
Starting the emulators in the development
Open the services/web/firebase/package.json
file. We need to change the dev NPM script so that it starts the Firebase emulators.
"scripts": {
"dev": "firebase emulators:start",
}
Let’s see this in action. In your terminal, navigate to services/web
and start the development environment with npm run dev
.
Note: If you do not have Java installed, you will be asked to do so, as this is a requirement from the Firebase emulators. You can find the latest version here.
In your browser, open http://localhost:4000 to look at the Firebase Emulator Suite. The Firestore tab displays your posts. Note, the database entries are deleted when you stop the dev server.
Gitpod users
In the Open Ports tab next to the terminal tabs, make sure you click “Make Public” for port 8080. This is so that the web application can connect to the local Firestore emulator.
Quick reminder, to access http://localhost:4000, open two terminal tabs. In the first tab, start Firefox by typing firefox and hit Enter. In the second, start the development server with npm run dev in the services/web directory.
Next, click on the “Open Ports” tab next to the terminal, locate 6080 and click “Open Browser” on the right.
In the new browser tab that opened, you can now use Firefox to access http://localhost:4000
Fix tests
One of our integration tests in services/web/cypress/integration/spec.js
expects at least one blog post to be displayed on the /blog
page. However, since we now use the Firebase Local Emulator Suite, our local Firestore data is deleted when we stop the emulator.
To fix this, we need to seed our database with a dummy blog post before we validate that the blog post is displayed on the page.
Install Firebase admin SDK
We start by installing the Firebase Admin SDK in services/web
:
npm install -D firebase-admin
Create a cypress task
Next, we need to create a Cypress task that allows us to insert a blog post before we run our test. Let’s begin with the task definition itself.
Create a services/web/cypress/plugins/firestore.js
file with the following content:
const admin = require("firebase-admin");
let firebaseApp;
let db;
const getFirebaseApp = (options) => {
if (!firebaseApp) {
firebaseApp = admin.initializeApp(options);
}
return firebaseApp;
};
const getFirestore = (firebaseAppOptions) => {
if (!db) {
db = getFirebaseApp(firebaseAppOptions).firestore();
}
return db;
};
module.exports = {
addBlogPost({ firebaseAppOptions, slug, post }) {
return new Promise(resolve => {
getFirestore(firebaseAppOptions).collection("posts").doc(slug).set(post).then(() => {
resolve(null);
});
});
}
};
The important part is the addBlogPost
function at the very bottom. Cypress
requires tasks to return an object or null. Hence, we wrap the Firestore operation in a new promise and resolve it with null when the document is successfully
inserted into the database.
The getFirestore()
and getFirebaseApp()
helpers allow us to cache the
initialized objects to speed up tests.
Add environment variables
In order for the Firebase Admin SDK to connect to the emulator Firestore, we can
use an environment variable that is picked up by the SDK. Open the services/web/package.json
file and add the following environment variable to
the beginning of the cy:run
and cy:open
NPM scripts:
FIRESTORE_EMULATOR_HOST='localhost:8080'
To make sure tests pass in our CI/CD pipeline, we also have to set that environment variable in the GitHub workflow. Open the .github/workflows/services-web.yml
file and add the following to the test job’s env.
FIRESTORE_EMULATOR_HOST: "localhost:8080"
Make addBlogPost
available in our test suite
We now need to register the addBlogPost
task to make it available in our test suite. To do that, open services/web/cypress/plugins/index.js
and update with the following two lines:
const firestoreTasks = require("./firestore");
...
module.exports = (on, config) => {
...
on("task", firestoreTasks);
};
Great progress! The addBlogPost
task is now registered. Open
services/web/cypress/integration/spec.js
.
Update tests
In the describe("Blog posts")
section, remove the beforeEach()
block and add the following before()
function instead:
before(() => {
cy.visit("/").then(contentWindow => {
const firebaseAppOptions = contentWindow.firebase.app().options;
cy.task("addBlogPost", {
firebaseAppOptions,
slug: "test-post",
post: {
content: "A test blog post",
title: "Test post"
}
});
});
cy.visit("/blog");
});
Instead of loading /blog
before each test (that’s what the beforeEach()
did), we will instead:
- Load the homepage
cy.visit("/")
. - When it is loaded with
.then()
, we receive the browser’s window object, which contains our Firebase app options. - Call the new
addBlogPost
task and pass the necessary parameters to create a blog post. - Load the
/blog
page.
Lastly, change the “displays blog posts” test to “displays the test blog post” and replace the cy.get()
line with the following:
cy.contains("[data-cy=blog-posts-list] a", "Test post");
This test now ensures the correct test post is displayed.
To make sure it all works, press RUN and follow the
Get hands-on with 1400+ tech skills courses.