Unit testing security rules with the Firebase Emulator Suite

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
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]
Info
Channel: Firebase
Views: 46,800
Rating: undefined out of 5
Keywords: unit testing, security rules, new firebase emulator suite, firebase emulator suite, firebase emulator suite tutorial, unit test security rules, how to unit test security rules, firebase local emulator, suite unit testing how to, database, security rules for database, how to test security rules, database security, what's new with firebase, firebase features, firebase, firebase developers, firebase live, type: Conference Talk (Full production);, pr_pr: Firebase, purpose: Educate
Id: VDulvfBpzZE
Channel Id: undefined
Length: 31min 23sec (1883 seconds)
Published: Wed Jun 24 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.