Firestore Development with TDD: Unit Testing Security Rules

Kentaro
Firebase Developers
5 min readJun 5, 2022

Overview of Testing in Firestore

There are two major test targets related to Firestore.

  1. Unit testing of Firestore Security Rules ← HERE🚀🚀🚀
  2. Unit testing of Cloud Functions triggered with Firestore updates ← coming soon…

This article introduces the former one: “Unit testing of Firestore Security Rules”.

The entire code presented in this article has been uploaded to the following GitHub repository. If you want to check the detail of the entire code, please refer there.

Pre-condition

It is assumed that the basic Firebase setup has already been completed.
Also, I assume that TypeScript is used in the entire code.

Since settings such as tsconfig.json are out of the scope of this article, please refer directly to the above GitHub repository.

Sample App Project Overview

Let’s assume that we are building a sample app that mimics Twitter.

Data Structure on Firestore

The Firestore data structure is the following simple form.

Data Structure on Firestore

users/{userId}

User info.

users/{userId}/tweets/{tweetId}

These documents are used to store the tweets of every user.
When a user’s page is accessed, these are used to show all of the user’s tweets.

tweets/{tweetId}

Store tweets of all users.
These are used to realize a Twitter timeline.

Expected Security Rules

The permissions for reading and writing each document should be the following specifications in this sample app.

users/{userId}

  • READ: Allowed for all users
  • CREATE: Allowed only for authenticated users
  • UPDATE: Allowed only for authenticated users
  • DELETE: Not allowed

users/{userId}/tweets/{tweetId}

  • READ: Allowed for all users
  • CREATE: Allowed only for authenticated users
  • UPDATE: Allowed only for authenticated users
  • DELETE: Allowed only for authenticated users

tweets/{tweetId}

  • READ: Allowed for all users
  • CREATE: Not allowed
  • UPDATE: Not allowed
  • DELETE: Not allowed

Since all document data in tweets/{tweetId} will be automatically synchronized with users/{userId}/tweets/{tweetId} by Cloud Functions, CREATE, UPDATE and DELETE should not be allowed in the security rule.
(Cloud Functions operations are performed in a trusted environment and will bypass Firestore Security Rules.)

Unit Testing Security Rules

Firestore Security Rules manage READ(= GET and LIST), CREATE, UPDATE and DELETE restrictions on each document data.

It’s pretty important to test security rules to prevent data from being deleted or edited unexpectedly due to incorrect client behaviors.

Here is the Google official document page.

While the READ restriction can be divided into GET and LIST restrictions, here we will write the test as READ.

Environment Setup

File Hierarchical Structure

We’ll build the following file hierarchical structure while you can modify it.

There is a file called firestore.rules in which you can configure Firestore Security Rules.

users_rules.test.ts and tweets_rules.test.ts are testing code for security rules and empty files for now.

Install necessary packages

Run the following command since a few packages are necessary to build a TDD environment.

Execute in the project root directory.

$ npm install --save-dev @firebase/rules-unit-testing @types/jest jest ts-jest

The official Firebase tutorial uses Chai as JS/TS testing framework, but I think Jest is more major, so we’ll use Jest.

Here is the entire code at this point.

Write Security Rules Test Code

Let’s write test code based on the expected security rules.

First, we’re going to implement users_rules.test.ts like below.

testEnv which is used for security rules test is initialized here. The projectId is a dummy project id (it doesn't need to be an actual Firebase project id).

Write Actual Test Code

First, we’re going to write test code to meet the following spec.

users/{userId}

  • READ: Allowed for all users
  • CREATE: Allowed only for authenticated users
  • UPDATE: Allowed only for authenticated users
  • DELETE: Not allowed

What do we do first?

Remember we are developing with TDD, so what we should do first is writing test code!

Initial user data is necessary to test READ, UPDATE and DELETE operations.
So, we’ll prepare the initial user data at first.

There is a function, withSecurityRuleDisabled() .
This is a function for testing and security rules are exceptionally ignored within the callback in this function. That is, we can write the initial data as we wish.

Now, the initial user data is ready!!

We’re going to write the actual test code as below.

Now, we have finished writing the test code.

We can simply run the following command to execute the test code in the project root directory:

$ firebase emulators:exec 'jest ./test/firestore/users_rules.test.ts'

Of course, this test should fail because we have not yet implemented the security rules code ( firestore.rules).

Then, here is the test code which can pass all tests:

In addition, the following is the Google official document on how to write the security rules. Please refer to it if you need more detail.

Write Test and Implementation Code for Others

I have written additional test and implementation code for other security rules though I skip explaining them here.

You can refer to this commit if interested:

Next…

That’s it!!

I’ll write the article to explain “Unit testing of Cloud Functions triggered with Firestore updates” next.

It’d be great and make me happy if you could give me a clap or comments🙇‍♂️

--

--

Kentaro
Firebase Developers

Full stack software engineer at VMware Tanzu Labs, working on front-end (React/TypeScript), back-end (SpringBoot/Kotlin), and infrastructure (AWS/Docker).