[MUSIC PLAYING]
YUCHEN SHI: Hello, everyone.
This is Yuchen from Firebase.
And I am engineer working on the emulation suite.
Today, I'm going to talk about how
to get the Firebase Emulator Suite working
in your continuous integration.
Well, that's a mouthful.
What it really means is using the tools provided by Firebase
to help you test your app and make
sure the app is always tested when you change something.
So talking about changes, let's say your working
on an amazing app, but, of course,
you are because you're using Firebase like when you
change something to the app.
Say one time, after you change it, you deployed to production
and suddenly you figure out your app stops working.
Yeah, that's stressful but that kind
of seeing happen to everyone, including myself.
But at Firebase with the Emulator Suite, we got you.
So what is the Firebase Emulator Suite?
Well, it's a set of tools designed
to help you spin up a local first, completely
isolated environment for testing your apps.
It's easy to see how it can really
help during local development.
But the Emulator Suite also lends itself
well in continuous testing and integration.
And I'm going to take you through the process of setting
that up.
So by the end of this video, you'll
have something like this on GitHub Actions.
Whenever you push any changes as you can see here,
it will always be tested against your comprehensive test suite.
So if you look at here, we push a new command
and, oh, no, it looks like a broke the test.
If you'd like to, you can drill into its logs
and see what it has are exactly broken.
So if anything could ever go wrong,
you can catch it before you deploy it to the real users.
And once you fix the issues, you can push again
and GitHub Actions will run your test suite again.
As you can probably tell from the screen check mark,
the test now pass.
And now you can deploy was much, much better confidence.
All right, let's take a look at the app we're working on today.
As you can see, this is a typical e-commerce website
where users can browse and add items to their shopping cart
like this.
Our app stores each user's shopping cart
in their own document in Cloud Firestore for Firebase.
And we use Firebase security rules
to make sure nobody can change someone else's shopping cart.
But what if with updating rules we make a mistake
and make it too restrictive so that users
won't be able to even edit their own carts?
You know what happens next?
Soon we won't even have a business anymore.
To make sure we absolutely get paid,
I wrote some unit tests to get this important piece covered,
using the Firebase Local Emulators Suite here's
my project layout with my security rules
here, functions here, and here's where I keep my tests.
You can find all the code and test on GitHub.
And I'll show you the link at the end of this video,
but let's take a look-- closer look as these tests for now.
So here the first test simulates a user
creating their own shopping cart and make sure it succeeds.
The second test make sure if they
try to modify someone else's cart, it will fail.
These tests run in the local, isolated environment,
this case, the Fire Store Emulator.
To learn more about how to write unit tests for your security
rules, check out this video by Todd and Rachel at Firebase.
So with these tests, we can make sure our security
rules are well-behaved but what about other pieces of our app?
For example, our website also calculates the cart total
for each user using a cloud function.
As you can see here, the cloud function
is triggered when shopping cart items
are updated in Fire Store.
It then calculates the total and, finally, writes the total
back to a Fire Store document.
We definitely don't want this to go wrong either,
since users put a lot of trust into their transactions
with us.
And that's why I've covered these kind of interactions
between different pieces with integration tests.
First, we add some items to a user's card in Fire Store
Emulator, of course.
This causes the function we just saw to trigger locally
in the functions emulator.
All emulators in the Emulator Suite
works nicely with each other.
As the function executes and writes back to Fire Store,
it only changes data in your Fire Store Emulator, not
production.
That's some nice isolation right there.
So if we listen to the cart document snapshot,
we will also see that the cart total has been
updated by the cloud function.
Once that happens, we can assert that the total
is what we're expecting.
And that proves our cloud function is also
working correctly.
With both unit tests and integration tests,
the full story of a user adding items to their shopping cart
is now complete.
Let's say we made some changes to our app.
For example, adding a new exciting feature.
Let's first make sure our tests still pass locally.
On the terminal, under your project directory,
type in Firebase emulators exec NPM test.
This emulators exec command is like emulators start, which
some of you may remember, but this command also
sets the right environment variables
so that your code using the [INAUDIBLE] SDK and [INAUDIBLE]
unit testing SDK will automatically
connect to the emulators with the right port numbers.
In many cases, this means you can reuse your production
code with no changes at all.
OK, let's rock and roll.
It's spinning up the Firestore and functions emulators right
now, and now it runs your tests after the emulator
suite is ready.
And all tests pass.
Nice.
Finally, you'll notice that the command will automatically shut
down the emulators for you.
Hey, that's really reassuring to know all tests pass.
However, it's really hard for me to remember to run this
every time I change something.
As my team grows larger, it is also
a pain to test everyone's changes together.
Here's when continuous integration, or CI,
comes into the play.
Continuous integration is the process
of ensuring your tests run continuously.
Whenever you open a pull request, push any new commits,
and before you merge, your carefully planned tests
need to run automatically.
This ensures that you can catch any errors that get introduced
as soon as possible.
Not when you have a large number of changes
that could be the culprit, but on every commit.
We run CI ourselves here at Firebase,
and I'd love to teach you how you can, too.
You can use any CI framework with the Emulator Suite,
but I'm going to do a deep dive into GitHub actions today.
For those of you who prefer Travis
CI or other environments, check out my blog post
for the instructions.
So let's start.
Let's first start by creating a GitHub actions configuration
file so GitHub knows what to do with our repo.
You do that by creating a .github folder under our repo
route.
Then create a subfolder named workflows.
That is where we will put the workflow configuration file,
and let's name it Firebase-ci.YAML.
If you already have workflow files,
feel free to create another file alongside your existing
workflow files.
All your workflow files will work in parallel.
Let's start by telling GitHub when our workflow should run.
We can go with a single on push, which
means running the workflow on every push to the repo.
We can also run it when pull requests are created.
OK, now let's create a job for this workflow called
emulator test, and we'll give it a memorable name.
We can then set the environments that it runs on.
For a list of options for the environment
and other configuration, please check out the GitHub actions
documentation.
And now we're ready to create our steps for the job.
So here are some basic steps to check out the repository,
setting up NodeJS, and installing Firebase
CI in a NodeJS environment.
Now onto our own project setup.
We manage our dependencies using NPM with this package.json file
right here.
So we want to install the dependencies using NPM install.
If you're not using NodeJS, feel free to swap the step out
with your own command.
However, keep in mind that we need
to tell GitHub to run NPM install within the functions
directory instead of the root directory,
because that's where we put our tests.
You can do that by using the working directory option,
specifying the relative pass from the GitHub repository
route to the project directory.
You might find this useful if your tests are in the functions
directory, or if you use a [? mole ?]
repo layout with multiple projects,
just like what we have right now.
Now we're ready to run our tests.
First of all, don't forget about working directory option
if you are running tests on their sub directories.
Now, type in the command, Firebase
emulators exec NPM test, which we explained a bit earlier.
This command starts the emulator suite
and then runs the command you specify and then shuts
the emulators down.
In our case, the command is NPM tests,
but you can use your own test command
if you're not using NodeJS.
OK, now we have the configuration,
it's time to save and push.
You will notice that we are developing
this on a branch called Firebase CI so we can test things out
without affecting the main branch and other pull
requests that go in.
Once we make sure it's working on our branch,
then we can safely merge to the main branch.
Let's see if our actions are running on GitHub.
Go to Github, select the Actions tab,
and refresh until GitHub kicks off the first action run.
There we go.
That's our commit message, and you
can see the workflow name here.
And you can also double check the workflow file config.
Yep, that's exactly what we just wrote.
We can monitor the progress by clicking on the task here,
and it shows you the steps running.
It's installing the CI, installing dependencies,
and running all the tests.
Oh, no, the test now failed.
What did we do wrong?
Let's dig a little bit more into the logs.
Oh, it says error, no currently active project.
Why does this happen?
It's because Firebase CI doesn't know which project
ID it should use on GitHub.
So
If we're looking into our Firebase JSON,
it only describes the services we're using,
but it does not contain the project ID.
The project ID is actually contained
in the FirebaseRC file, which specifies the default project
ID, example, in our case.
This file is usually ignored by the .gitignore file,
which means it is not uploaded to GitHub and not included
on fresh checkup.
That's why the Firebase CI won't be
able to know which project you need to run this on.
And how do we fix that?
Well, simple.
We just need to edit the YAML file, or more precisely,
the final step.
Add the --project flag followed by our project ID.
You can use any project ID here, it doesn't even
have to be real, but keep in mind
that this needs to match the project ID
you're using in your test.
For example, our test writes Firestore documents
in the project ID example here.
And remember earlier, we wrote a test
that asserts our cloud function is triggered.
In order for the cloud function to actually trigger,
you need to specify the same project ID on the command line.
Otherwise, some tests may timeout
or fail due to the functions not running.
When in doubt, you can just use your real project ID everywhere
to keep it consistent.
Don't worry, it won't actually hit production.
Now that we fixed the project ID, time to push again.
So now, if you go to the GitHub Actions page,
you will see GitHub already picked up the new commit,
and is running the tests right now.
And right now you see all tests passed.
Great.
Yep, it's just like that.
So if you push any commits to GitHub,
it will run the actions on those commits.
You can always see a history of all runs on this Actions page,
as well.
In this way, you know which commit broke the test.
And then you can fix it with a follow
up commit and make sure everything is working again.
Before we wrap up, note that we're only testing on a branch
right now.
Once you make sure the CI is working,
you would definitely want to merge this to your main branch.
After that, any pull request to your main branch
will also be covered.
That's pretty much it.
Now you can build an app with much, much better confidence.
To recap, here's what we did today.
We talked about how we used the Emulator Suite to make
sure our app is working correctly using unit
tests and integration test.
We then showed you how you can use GitHub Actions to run
continuous integration running these tests as soon as you
change something.
You can find our example app code, including all tests,
and get a workflow configuration here.
I hope you're now ready to write some tests
and get them running on CI.
If you're watching this and saying to yourself,
uh, I'm just getting started on my app,
I'm not really at a stage of integration testing, here's
a bonus for you.
Check out this video on how to use our shiny new emulator
UI for iterating on an app by my colleagues, David and Tyler.
This is something you can use even
from day one of app development.
You can also use it for manual testing,
giving you even more coverage and confidence.
But either way, make sure to test often and test before you
deploy.
Remember, building apps is building user trust,
and Firebase is always here to help.
Catch you later.
[MUSIC PLAYING]