TODD KERPELMAN: Security rules-- they're important to get
right, but traditionally, they haven't been the easiest either
to develop for or to test. Luckily, we've been
making some improvements, both in the Security
Rules language itself and the Emulator Suite to
make this whole process better. Let's find out more on
this episode of Firecasts. [MUSIC PLAYING] So security rules are important. They sit on the front
lines of your app, and they're a big
reason you can let all those untrustworthy
clients talk directly to services, like Cloud
Firestore or the Realtime Database, for a truly
serverless experience. But they've been difficult
to create and test, at least historically. Thankfully, things
have gotten much better in the last few months. For starters, we've made
a number of improvements to the Security
Rules playground, which you can access here
in the Firebase console. And if you haven't
seen it lately, I encourage you
to give it a try. And we've also added
a lot of new features to the language itself, which
will make a lot of formerly difficult tasks much easier. And I will be covering some
of these in a follow up video. But more importantly, we've made
some significant improvements to the Firebase Emulator Suite
over the last several months. And if you're planning
on doing any development against the real time database,
Cloud Firestore, or Cloud Functions, I would
encourage you to start by developing and
testing everything locally against the emulators
before you even touch your instances in the cloud. [SOUND EFFECT] This kind of emulator
first development process will make life easier for
you, and you'll probably see us do more of
this in future videos. So here's the plan. In this episode,
we're going to cover getting started with unit
testing in the Firebase Emulator Suite, and we'll cover
some of the basic concepts of security rules. In the next video, we'll go
over some of the newer features and security rules
that make it easier to do things, like write
more complex functions, and debug your rules
when stuff goes wrong, and perform common tasks
like restricting edits to certain document fields. Now if you want to
follow along with what I'm doing in this video, here's
what you're going to need. [SOUND EFFECT] First, a halfway decent
text editor or IDE. I'm using VS Code with
the Firebase rules syntax highlighting
extension enabled so I get some nice highlighting
and code completions. Next, Node and the
Node Package Manager, which you get from nodejs.org. You'll need this for
running your unit tests. A somewhat recent
version of Java. I don't actually know what the
precise minimum version is. I currently have
11.04 installed, and that seems to work. And an up-to-date version
of the Firebase CLI tools. And yes, make sure this
is a recent version since a lot of the
more recent changes are specifically centered
around the Emulator Suite. And you can grab
the latest version by running npm -i
g firebase-tools. And finally, I've gone ahead
and created an empty project in the Firebase console,
and enabled Cloud Firestore in production mode, meaning
my security rules are set up to reject everything. So let's talk about the app
that we are making in theory. Let's assume that
we're making some kind of basic social networking app. Our database will have
a user's collection with a document to store
information about each user. We'll have a post collection
where users can share their thoughts with the world. And then let's say some kind
of read-only collection, someplace we might want to store
data that our app occasionally needs, but shouldn't be
written by our users. Seems like a simple
enough setup, but let's think about
the security rules we're going to need
to support this. Now our read-only collection is
probably the simplest use case. Maybe we want to say
that anybody in the world can read from it, but no client
is allowed to write to it. Well, these rules seem pretty
simple to build, but how would we test them? I mean, we basically
need to test two things. One that a logic
client acting normally is able to access the things
it's supposed to, and two, that a hacked client making
unauthorized calls isn't able to access the things
it's not supposed to access. And that's where
things can get messy. I mean, yeah, I could
write my own hacked client app to specifically make
calls to my database that it shouldn't,
and see if those calls get properly rejected. But, well, that's a lot
of manual work and setup, and do I really want to run the
risk of accidentally corrupting my production data? Probably not. As this is where
unit tests can help. I'm not going to give you a huge
overview of unit testing here. There's plenty of
tutorials out there that tell you all
about that already. But basically, you
can kind of think of unit tests as
a battery of tests that you run automatically
to make sure your app works the way it's supposed to. And this battery
grows over time. Every time you add a
feature or fix a bug, you also write a test to
confirm that this feature works the way it's supposed to,
or that the bug is fixed. And this means that
in the future, if you need to make big changes to
your code or refactor something, you can do that
while still having the confidence that your
code still works the way it's supposed to thanks
to all these unit tests. Now when it comes to unit
testing in Cloud Firestore, I'm going to run these
tests against a local copy of our database that
I'm going to get running right here on my laptop. Why? Three reasons. First, it's faster. Since the tests in the database
are all running locally, there's no network
communication needed, so these tests end
up nice and speedy. Second, it's safer. I can add placeholder documents,
perform unauthorized writes, or even wipe our data and not
worry about messing up anything in my production database. And finally, it's
a little cheaper. These reads and writes
don't really cost me anything, so I
don't have to worry about an out of
control unit test accidentally running up costs. That's enough talk. Let's get coding. [SOUND EFFECT] So if you haven't
done this yet, create a directory for your project. I'm going to make
sure I'm logged in by calling firebase login
from the command line. And then I will
call firebase init. This will set up
some local files that will correspond
to the project that I've created in
the Firebase console. So let's see. I'm going to first select
Firestore and Emulators since those are the projects
I'm interested in working with here. You can always
add in more later. Next, I'll ask to use
an existing project. And I will pick that project
that I set up earlier in the Firebase console. Now I'll go with the default
file name for security rules and indexes. And then I'll ask to set
up the Firestore Emulator. I'll pick the default port
and say, sure, go ahead and download the
emulators, but I'm pretty sure I've done this already. Yeah, that was fast. OK, great. So there's not much
here at the moment. The most important
thing to notice is that it's downloaded
the firestore.rules file for my project. And so from here
on out, I'm going to be updating my security rules
by changing them here, and then calling firebase deploy when
I'm ready to upload it again to the server. And I kind of recommend
you do the same when you're developing for
reals instead of writing rules directly in the console. In addition to being able
to test and develop locally, this also lets you do nice
things like check your security rules into your version
control alongside the rest of your code, and deal
with merge conflicts, and all that fun stuff. So to start testing
my rules, let me create a directory
called test here where I will place my unit tests. And in there, I'll type
npm init to set things up. Now you'll notice that
I'm sticking with most the default values here. I will make my main
entry point test.js since that seems appropriate. I think the important
thing to note here is that for my test command,
I'm going to use mocha --exit. If you've never
heard of Mocha, it's basically a JavaScript
test framework that makes writing unit
tests a lot easier. This is not any kind of
official endorsement by the way. I'm sure there's plenty
of good ones out there. This just happened to be the
one that my coworker was using, so I kind of went with that. Finally, I should install a
couple of libraries in here to get started. I will call npm install mocha
--save-dev to install the Mocha testing framework. This save-dev thing basically
means only save this library for development purposes--
don't deploy it with my code. And I'm going to do the same
thing with @firebase/testing. We'll use this library to
easily test against firebase. And now we are ready
to run our first test. Let's open up the parent
directory in VS Code so I have easy access
to the security rules. And then in my
test directory, I'm going to create a new
file called test.js, and I'm going to write
our very first unit test. Now a Mocha file
is what's called a behavior driven testing style,
where you generally describe all the things
your app should do, and the right tests
that make assertions around each description. Maybe that was confusing,
so perhaps an example would be best. First, I'll start by bringing
in the assert module. This should already
be installed globally. Next, I'll describe
our social app using the function describe. This is kind of a container for
all of our other unit tests. And in the callback
here, I'll add the things that my social app should do. For example, let's say that it
should understand basic math. So I'll use this special
function called "it" where I describe the thing
my application could do. And then I will write
a callback function to support this task,
which generally has some kind of assertion like so. Then from my test directory,
I will call npm test. It will run this test.js
file, and confirm that every assertion in my unit
test is true, and we're good. Our app understands basic math. Hooray. And if I were to set this
to a different number and then rerun my tests, you can
see now that my test now fails. So this is fine and
dandy, but we're here to test security rules,
not first grade math. So let's try something
that actually interacts with our database. So first off, I'm going to
bring in the @firebase/testing library we installed earlier. Next, I'm going to go ahead and
set a project ID to a constant. For now, make this
your actual project ID from the Firebase console. This will ensure that new
changes to the security rules get picked up automatically
by the emulator every time you change them. Now, there are sometimes
reasons not to do this, but for now, this is
certainly easiest. Finally, let's
write a test to see if we can read from
our read-only section of the database. The first thing we'll do
is initialize our database, and we can do that with the
initializeTestApp command. And you can see that I'm
passing in my project ID to the test app. Now once I have the
test app, I will call firestore to get
at the Cloud Firestore instance of my Firebase app. Then we can create a reference
to our test document. It's in a collection read-only,
and let's call our doc, say, testDoc to be real original. Now I'm going to try to read it. This assert succeeds is another
method in the Firebase testing library that asks Firebase
to attempt to make this call. In my case, getting the
document and asserting that it is successful. Now even though I'll be
testing this locally, this is still considered
an asynchronous call, so I'll need to
add in await here. If you've never
seen this before, this is basically
telling our code to not proceed any further
until this call is done. But in order for
that to work, I'll need to label this
test as asynchronous, which I can do like so. By the way, if any of this
async await stuff is confusing, go check out this video
linked in the description below where Doug
explains async/await in a lot more detail. But definitely get
used to this pattern. We use it everywhere
in our unit tests. So let's run our test again with
npm test, and we get an error. Why? Well, let's see. According to our error
message, the client is offline. Basically, our
database isn't running. So let me open up a
separate command line, head into my project directory,
and then start up our local emulators by calling
firebase emulators:start. And this will start up a local
instance of Cloud Firestore. And if I had hosting or cloud
functions set up on my project, it would start up local
instances of those as well. So let's go back and rerun
our test, and it fails again. Why? Well, because we still have
our security rules locked down. So actually, our test is
proving helpful in that it's reminding us that
our rules aren't working the way we've expected. Now maybe I could fix
this by setting my default rules to true, and then just
locking down the private areas. RACHEL MYERS: Oh,
no, don't do that! TODD KERPELMAN: Oh,
why, it's Rachel Myers, developer programs engineer
on the Firebase Rules team. RACHEL MYERS: Hey. Your security rules should
follow the principle of least privilege. TODD KERPELMAN: Principle
of least privilege? What's that? RACHEL MYERS: It's
the idea that you want to grant just enough
permission for users to use your app, but no more. TODD KERPELMAN: OK, so
what are some good examples around this beyond maybe not
just setting your security rules to be globally
readable by default? RACHEL MYERS: So say
you wanted to give users the ability to edit a document. If you can, start by
giving them the ability to edit only certain
fields rather than the entire document. And depending on how
often you add fields, consider making it a whitelist
instead of a blacklist of banned fields. TODD KERPELMAN:
That's a good idea. We should show people
how to do that, huh? RACHEL MYERS: Next video. TODD KERPELMAN: OK, fine. So maybe the right
thing to do here is keep everything
locked down by default, but open up our read-only
directory just for reads. So let's do that. [SOUND EFFECT] So as you might
be already aware, rules are generally made
up of match blocks that describe which part of the
database this set of rules pertains to. Inside this block, we
allow certain actions if some condition
turns out to be true. So for our read-only
collection, we'll create a match block
like this where we'll match the collection read-only. And these curly brackets
mean match any document ID within that collection. As for our conditions,
they're really simple. I'll allow reads
if true, and I will allow writes if false,
which means nothing can be written in
this collection. So we will save our
rules, and notice how our emulator has
noticed this change, and updated its
security rules already. So now, we can rerun our tests. And hey, look at that. Our test now passes. Now some of you
might be like, hey, how is this test
passing if I don't have a document called testdoc? And the answer
here is, basically, that the call itself succeeded. Sure, what we're getting back
is an empty document snapshot, but we're successfully
getting back that empty snapshot instead of
the request forbidden error, and that's why this test passes. Oh, while we're
here, we should also add a test to make sure
that users can't write to our read-only collection. So I'll basically set
everything up the same as in the previous test. We will change the
description, and maybe I'll use a different document
ID here just for fun. But then for the last line here,
instead of assert succeeds, I'm going to use assert fails
for the call testDoc.set, and let's just add a
little fake data in here. Now as you might
expect, this assertion will pass only if the
attempted action fails. So let me rerun my tests. And yep, looks like my
write attempt fails, and therefore, my test passes. [SOUND EFFECT] We've got some basic tests
and security rules down. Let's move onto something
slightly more interesting. [SOUND EFFECT] Let's look at our
user documents. Now we're going to use Firebase
auth that will assign a user ID to every user that signs in. And we're going to set up
our documents in the user collection, such that
the ID of the document is the ID of our signed in user. So we probably want to say here
that a user can write to a user document, but only if
the ID of that document is equal to their user ID. This "only let a user
access a document that's got the same ID as
their ID pattern" is very common in security rules,
so this is a good example to cover. Now obviously, our
rules for handling this are going to be a
little more complex than for our
read-only collection. It needs to be a little
more conditional. So in general, with
most rules, you're going to be looking at three
important chunks of data. The first one which
I'll talk about here is the request object, which
represents the request coming from the client. This in turn contains
an auth object, which includes important
information about the user like their user ID or email. [SOUND EFFECT] Now you might be wondering,
hey, if this request.auth object is coming from the
client, how can I trust it? RACHEL MYERS: Oh,
I can answer that. You see, this information comes
from an ID token generated by our auth server
that gets passed along with every client request. This token not only contains
information like the user ID, but also a signature signed
by Firebase's private key. Our rules server can
quickly verify the signature against Firebase's
public key, which means that we can be sure this
user ID is the same one that's being generated by
our auth server. TODD KERPELMAN: Well,
that makes sense. Let's start using it. [SOUND EFFECT] So if we were to write this
out in our security rules, it would probably look
something like this. We'll want to grab any document
in the user's collection, and this userId in brackets
here is basically saying, take the ID of the
document and store it in this variable called
userId that I can use later in this block. Technically, this is
known as a capture. So let's allow people to
write to this document if the request.auth.uid is the
same as this userId variable I just stored, or
captured, earlier. So this seems like a good rule. Let's test it. So let's see here. I will describe
this rule as saying that our app can
write a user document with the same ID as our user. I'll initialize our
database just like before. I'm going to create the
document that I want to write. It's going to be a document
called user_abc in our users collection like so. And then I will say await
firebase.assertSucceeds, and then I will
call testDoc.set, and add in some dummy data. OK, great. So let's test this,
and it doesn't work. So why not? Well, probably the
answer is obvious here. I've said that you can
only create a document if the document ID is
the same as your user ID, but nowhere have I signed
myself in as user_abc. Now luckily, this
is an easy fix. In addition to passing a project
ID to our initialize test app call, I can also pass
in an auth object. This is basically
telling Firebase, hey, for the sake of
this test app just pretend like I'm signed
in like this user. Now I can add anything I need
to onto this auth object, and it will be passed
along to the security rules as the request.auth object. And in this case, the most
important thing to pass along is the uid field to set the user
ID of our theoretically signed in user. So let me say myAuth is an
object where uid is this user-- user_abc. And heck, let's give them an
email address too just for fun. Ooh, that was fun. I need to get out more. And now I can go ahead
and add that auth object to my test app. Now when I'm running
my unit tests, Cloud Firestore will pretend
like it signed in as this user. And now our test works. Hey, nice. Similarly, it's pretty easy now
to make sure you cannot write to a document where
you are not the owner. Let me copy and paste
our previous test. We will change the description
to say we are a different user, change our target document
to one belonging to user_xyz, and make sure that it fails. And that passes to. [SOUND EFFECT] By the way, some of you
test-driven development purists out there might have
noticed that I've been kind of doing things
in the wrong order. The general idea with
test-driven development, in theory, is that when
you're adding a new feature or fixing a bug, the
first thing you should do is write a test that fails
because that feature hasn't been added or that bug exists. Then go ahead and implement
that feature or fix that bug, and then show that the
previously broken test now works. Now I'm not really going to
be doing that in this video. For the most part, I'll be
writing the security rules first, and then writing
the test for them. And mostly, I'm
doing this because I think for the purposes
of teaching you some of the concepts around security
rules and testing them, I actually think
that's a better order to understand what's going on. Also, honestly, security
rules are a little weird in that an operation either
has to succeed or fail, and our tests are
a mix of operations that either succeed or fail. So even without writing
any security rules, half your tests
will end up passing but for the wrong reasons,
which I know is kind of weird. And maybe there are better
principles around doing this. Rachel, any wisdom? RACHEL MYERS: If it
helps you to think about what your rules should
be by writing your test, especially the test
descriptions, then that's great. That's kind of how
I like to work. If it's easier to
write the rules first, that seems fine too. But like all tests, it's
important to make sure that when they pass, they're
passing for the right reasons. [SOUND EFFECT] TODD KERPELMAN: Anyway, let's
also take a moment to refactor. I'm already starting to see
a lot of repetitive code here along with some
hard coded strings, and so I'm going to pull some of
these out into some constants. Let's see. The first thing I'm going
to do is create a constant for user_abc called myId,
and another one for user_xyz called theirId. I find that for a lot of
security rules testing, this kind of myUser theirUser
pattern pops up a lot. Next, I will create
myAuth, which will be the user object
representing myId. And finally, I'm going to create
a function getFirestore, which runs Firebase test app
against my project ID, and with an optional
auth object that gets passed in the function. Basically, something like this. So now I can call getFirestore
with an argument of null everywhere in places where I
don't need to be signed in, and getFirestore with
myAuth in all the places where I want to be signed
in as my test user. And I can go ahead and replace
those hardcoded userId strings elsewhere in the code. This is already starting to
look quite a bit cleaner, so I'm happy. Let me rerun my tests here
and make sure they all work. And it looks like they do. [SOUND EFFECT] So clearly, there's
a lot more we could be doing with
our user documents, but let's move on to our posts
because I want to show you a few more things there. [SOUND EFFECT] So let's say that our
posts, in addition to storing whatever content
our users have written, also have an author ID field
and a visibility field that determines whether this
document is public or private. And let's say that
we, as app developers, have determined that a user is
allowed to view any post that belongs to them-- that
is, they're the author-- or one that's marked public. So how can we write
rules around this? Now for this set of
rules, we're going to take a look at the
second piece of data you're often going
to be dealing with, and that is the resource object. This represents the currently
existing object in the database that you are trying to access. Now, the vast
majority of the time, we're interested in the
content of this document, which you can access by looking
at the resource.data value. This will return
all of the fields in the document as a map-- basically, a set
of key-value pairs. So for instance, if I
were interested in finding the value of this
visibility field, I would do that by
looking at the value of resource.visibility
in my security rules. In fact, you know what,
let's do that right now. [SOUND EFFECT] So let's go back to
our rules document, and I'm going to add some
rules to our posts collection. And we'll allow reads if-- let's see here-- the
resource.data.visibility equals public. Again, that's the
value of the visibility field in the document
I'm trying to read. Or the resource.data.authorId
equals the verified user ID of our user, the
request.auth.uid. So this looks like
it would work. Let's write a test to see. So we can read posts
that are marked public. So let's initialize the
Firestore app as a not signed in user. I'll create a test query,
where we're looking for documents marked public. And then assertSucceeds,
testQuery.get. Now let's run
this, and it works. [SOUND EFFECT] Now this might
surprise some of you. You're like, wait, how
is this able to run? We don't even have
a post collection, so how can we see we have
any documents marked public? [SOUND EFFECT] And the answer to this has to
do with the way security rules work on queries. You see, when we're
running a query, security rules don't
have time to analyze every single document that
gets returned from that query-- not if we want our
security rules to like evaluate everything
within nanoseconds for a performant
database experience. Instead, our security
rules have to prove that this query would
be allowed no matter what is in the underlying data. And it turns out, we
can't prove that here. Because we're only searching for
posts with the visibility set to public, then by
definition, any documents returned would be allowed by
this part of our security rule. [SOUND EFFECT] So here's another example. I've created a test where
I'm querying for posts written by our author. Notice that I initialized
a Firestore app with myAuth object. And again, we can prove
that the documents returned by this query would be
allowed by our security rules no matter what other
underlying data is in there. But what about this one? What if I'm just querying all
the posts in our collection? I have the setup
as assertSucceeds, and it turns out this
test does not pass. And that's because
even if I had it setup where all my posts
were set to public, Security Rules is telling
me that this query would not be allowed in all cases
without having to first look at the underlying data. There could be documents
marked as private. And if some document can't
pass our security rule test, then the entire
query is rejected. So the correct thing
here is to actually mark this as assertFails. My rules are right. It was this test that was wrong. Now I will rerun my
test, and that is better. Let's look at a slightly
different use case, and that is reading
one specific document. So once again, let's access our
database as a signed in user. And we'll look at a specific
document called public_post. And we'll want to say that
reading this post succeeds. Now in theory, this test
should work if our document were listed as public. This is an individual
document get, so unlike the query
examples above, we are allowed to look
at the underlying data. So if we have a document
with the ID of public post whose visibility is set to
public, this test will pass. But I run it, and
of course it fails. And that's because this
theoretical document doesn't actually exist, so there's
no visibility field to check. So how can we add one? Well, I don't know if
you noticed this earlier, but there's this line
here when you start up the Firebase Emulator that
links to the emulator UI. And what is that? Well, let's find out. Oh, look at that. It looks like we can access
our emulators directly in our browser. And if I click on the
Cloud Firestore one, hey, look at that. It's like the Firebase console
for my very own local instance of Firestore. And I can interact
with this very much like the Firebase console. So let me go ahead and
create a post collection. And I'll create my first
document in that collection. We'll name it
public post, give it an author ID of, say,
user_xyz, and most importantly, a visibility set to public. Now that this document
is in my system, I can go ahead and rerun
that same unit test, and it now works. Now that's great,
but do I really want to have to go
into my local emulator and create this document in
the UI every time I start up? Eh, probably not. Using the UI alongside
the Firebase Emulator is super useful when you're
developing client code and you want to do
things like look at the documents in the database
as your code is producing them, or maybe you want to create a
document with specific kinds of data to kind
of test your code and make sure your
client can handle it. But obviously, for
running unit tests, this is not really something
I want to have to do manually. So this probably isn't the
right tool for the job, but we're all just really
excited about this new emulator UI, and we wanted to show it
off because it's pretty nice. But I'm going to go ahead
and delete this document and go back to our code. Now it seems like what
we really going to do is create this document
first in our code-- maybe something like this. But as you might suspect,
this test also fails. Why? Actually, it tells
us right here. It failed at line 5
in our security rules. And so if we jump to
that line, well, those are our default
rules for evaluating any kind of write argument. And you can kind of see
that our security rules system had to bubble up
to these default rules because there were no
other rules in our posts section for creating
or editing a document. But you know what, even if
there were, they'd probably come with some kind of
restriction like, hey, you can't write a post where
the authorId is somebody else. So even then, trying to
create this document belonging to another person is problematic
right now in our test app. So we're going to use one
other very useful feature in the Firebase testing library. In addition to initialize test
app, which creates a Cloud Firestore client app, there's
also initialize admin app which creates basically an admin
version of Cloud Firestore, one that is allowed to bypass all
of our client based security rules. So let me create a new
function, getAdminFirestore, which returns
firebase.InitializeAdminApp with our project ID. And we'll get
Firestore from that. And now back in
my original post, I'm going to start by saying
admin equals getAdminFirestore. I'm going to move our
postId into a variable just to avoid using
hardcoded strings too often. Then let's say that our setupDoc
is equal to admin.collection, posts, dot doc, postId. And now we can
call set on it like before with another person's
user ID and public visibility. Anyway, now in my code
below to read this document, I will just swap out
my hardcoded postId with this variable. And I really should be
calling this testRead instead of testQuery because we
are just reading a single doc. Note, by the way,
that I have to have two different variables
that essentially access the same document. One that's accessing it as an
admin kind of in sudo mode, skipping rules, and
one that's accessing it as a user going
through security rules. And it's kind of annoying,
and I haven't really figured out a more
clever way around it beyond maybe putting
my entire document path into a separate variable. But what are you going to do? Anyway, let's rerun my test now. Looks like my test passes. And if I Alt-Tab over
to the emulator UI, there's my test
document right there. Cool. And with a little cutting
and pasting goodness, I can create a test to make
sure I can read a private post, assuming that I am the
user that created it. Note that I am calling
getFirestore with myAuth here in this line. And I can create a
similar test to make sure I can't view a private post
that belongs to somebody else. And I can run
these tests, and it looks like they all pass too. That's nice. Now this is all great, but if I
look over at my emulator page, things are starting
to get messy. I have a lot of leftover
docs here and there that could quite possibly
mess up future tests. Given that I'm
creating the docs I need at the beginning
of each test, I should probably just clean
up everything and start from a clean slate every time. Luckily, Mocha and the
Firebase testing library lets us do that pretty easily. Mocha has this
function called "after" which lets us perform
certain actions at the end of all of our tests. And so if I were to call
firebase.clearFirestoreData with our project ID like this--
woops, let's make it async-- then the next time
I run my unit tests, Mocha will helpfully
clean up my database after all the tests have run. Let me switch back
to my emulator, and it is once again empty. And that's good,
but I probably want to start with a clean slate at
the beginning of every test, not just when the
entire suite is done. And luckily, Mocha
has a function for that to called "beforeEach." So let me add a call to that,
and I'm going to put this up near the beginning of my file. And I'm going to have a
clean and empty database to work with before every test
and after the entire suite is run. And this is good. It means I can create
only the documents I need to run my tests
within each test, and you don't have to worry
about weird side effects from any previous tests. And I recommend you get
into this habit as well. It will make your
life a lot easier. [SOUND EFFECT] Well, this seems like a pretty
good stopping point for now. So far we have learned-- [SOUND EFFECT] How to get the Firebase Emulator
Suite working on your machine, how to set up some unit
tests with NPM and Mocha, using the Firebase
library to test that calls succeed and fail as expected. Passing in an auth
object to simulate acting like a signed
in user, understanding that when it comes to queries
we need to prove a query is allowed without looking
at the underlying data. Using the firebase.adminApp
to alter our database without the constraints
of our security rules. And using test
helpers like after and beforeEach to
ensure that we have a nice, clean setup every time. In the next video, we'll
start investigating some more complex
security rules features, including adding
role-based security and using some of the new
rules language features, like list, set, and map, to
ensure that only certain fields can be written to. Creating custom rules functions,
some simple debugging tricks, and maybe more. It kind of depends
on what questions you ask me in the comments. [SOUND EFFECT] Phew, we made it. Thanks for watching. An extra special thanks to our
extra special guest presenter, Rachel. And we will both see you soon
on another episode of Firecasts. [MUSIC PLAYING]