Be the first to know and get exclusive access to offers by signing up for our mailing list(s).

Subscribe

We ❤️ Open Source

A community education resource

7 min read

Testing Firestore security rules without touching production

Learn how to use Firebase emulators and testing libraries to validate your security rules before deployment.

Firestore makes it extremely easy to access data directly from your applications. There are libraries ready for most major languages. That flexibility comes at a cost, however. Since the applications can modify and read the database directly, you need to think about your database like a public API. Thankfully, we can write security rules to enforce access restrictions on the data. This is important for privacy, but also making sure the data is only modified in the ways we want to allow.

I’ve used Firebase now for multiple projects and love how quickly I can get projects moving, but eventually I’ll need more complex security rules. Developing these directly in the Firebase console is not a great option. I want to know that changes that I’ve made aren’t going to break existing functionality. I’m also not a huge fan of running tests against production data.

While Firebase itself isn’t open source software, the Firebase team has released an open source NPM testing module we can use for testing against our security rules. It can be used in existing Javascript tests or we can set up an independent project solely for testing. I’d normally prefer to keep the language the same as my development language (Dart with Flutter), but the only library provided at this time is for Node.

A complete guide to Firestore security rules testing setup

What we’ll do is set up an independent testing project that can be used regardless of what language the actual project is setup in. We’ll need to add emulator support to our existing firebase project. Then we can begin adding tests. We’ll add some test data, and then another test that mocks an authenticated user. The components of those tests can then be used as needed to make additional tests in your project.

Before these steps will work for you, you’re going to need the following installed on your machine:

  • NodeJs
  • NPM
  • Java
  • firebase-tools NPM package

For these tests, we’ll use a simple example schema for a messaging app:

Messages { sender: string, recipients: string[], content: string }

Set up the Firestore emulator

If you have already set up the emulator for your project, then you can skip ahead. To setup the emulators, you’ll need to run:

> firebase init

Select Emulators from the list and then select the Firestore Emulator. The rest of the settings can be left at their defaults. Let it download the emulators and then you’re ready to start them:

> firebase emulators:start

Once it’s up and running, you can access it at http://localhost:4000/. If you click on the Firestore tab, you’ll see an interface similar to what you get in the Firebase console for the project:

Firestore security rules screen capture emulator UI

Create your test project

Now that the emulator is up and running properly, we’ll create the test project. I’m going to create it in a subdirectory of my existing project:

> mkdir rules_testing
> cd rules_testing
> npm init

Go ahead and set up the test command to “mocha –exit tests/*<em>/</em>.spec.js” and make sure to set the project type to “module.”

mocha --exit tests/**/*.spec.js
Firestore security rules screen capture project type module

Now we’ll install our dependencies:

> npm install --save-dev mocha @firebase/rules-unit-testing

These tests will assume that your firestore rules are being stored at the root of your existing project in your firestore.rules file. Here’s what the one from this project looks like:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if false;
    }

    match /messages/{messageId} {
      allow read, write: if request.auth.uid != null && 
        (request.auth.uid in resource.data.recipients || request.auth.uid == resource.data.sender);
    }
  }
}

Nothing too complicated here. By default, access is denied to any document, and then we are allowing access to message documents only if the authenticated user is either the sender or the recipient of the message. Let’s start adding some tests now!

Start adding tests (and data)

We’ll start by making sure that someone who is not authenticated can’t read a message. Create your first test in tests/messages.spec.js:

import { describe, it } from 'mocha';
import { assertSucceeds, assertFails, initializeTestEnvironment } from '@firebase/rules-unit-testing';
import fs from 'fs';

const projectId = '<<replace with your project id>>';

describe('Firestore Rules', () => {
  it('should NOT allow unauthenticated users to read messages', async () => {
    const testEnv = await initializeTestEnvironment({
        projectId: projectId,
        firestore: {
            rules: fs.readFileSync('../firestore.rules', 'utf8'),
            host: 'localhost',
            port: 8080
        },
    });
    const publicUser = testEnv.unauthenticatedContext();
      await assertFails(publicUser.firestore()
        .collection('messages')
        .doc('1').get());
  });
});

The general flow for writing these tests is to start by initializing the testing environment, and then setting up the context for the test to run in (authenticated or unauthenticated). We’ll use the project id from Firebase (might look like ‘my-project-ab20e‘). Then we’ll be sure to pass the rules in so they can be evaluated. For this test, we’ll make sure to use the unauthenticatedContext.

Run npm test and the it will pass:

Firestore security rules screen capture npm test

But we can’t be sure it’s passing for the right reasons. Right now, that data doesn’t exist, so reading the data could be failing because of missing data. We’ll need to add data for the tests. We can do this manually, but to make testing easier, we can do this through the testing library.

The library provides a way for us to get around the security rules for the purposes of test setup. This is convenient since you may need to preload data that isn’t writable per your security rules. The withSecurityRulesDisabled method allows us to provide a callback which is run in a context where the security rules are disabled:

before('Re-load data', async () => {
       const testEnv = await initializeTestEnvironment({
           projectId: projectId,
           firestore: {
               rules: fs.readFileSync('../firestore.rules', 'utf8'),
               host: 'localhost',
               port: 8080
           },
       });
       await testEnv.withSecurityRulesDisabled(async (context) => {
            await context.firestore().collection('messages').doc('1').set({
               content: "I'm a message!",
               recipients: ['my_user'],
               sender: ''
           });       
       });
   });

If you rerun the test, it should pass. Now you can verify now that the data exists by looking at the emulators UI:

Firestore security rules screen capture emulator UI with data

We know data exists but the write fails because the firestore rules will not allow the write to work. This is great, but now we want to ensure that the rules will not prevent access to legitimate users. Let’s add a test to be sure that the recipient can read their messages:

it('should allow authenticated users to read their received messages', async () => {
    const testEnv = await initializeTestEnvironment({
        projectId: projectId,
        firestore: {
            rules: fs.readFileSync('../firestore.rules', 'utf8'),
            host: 'localhost',
            port: 8080
        },
    });
    const myUser = testEnv.authenticatedContext('my_user');
      await assertSucceeds(myUser.firestore()
        .collection('messages')
        .doc('1').get());
  });

Run npm test and check the results:

Firestore security rules screen capture npm test results

Excellent! Obviously not a comprehensive test suite yet, but this provides a good starting point. We’re able to interact with the database as either a known user or as an unauthenticated user. We can even modify the database outside of the rules as needed for test setup. Firestore security rules have a number of powerful features, including a number of math and set operations. With a test suite in place, you are now prepared to begin iterating on your rules locally as well.

You can see the full demo at https://github.com/chris-barile/demo-firestore-rules-testing

More from We Love Open Source

About the Author

Chris Barile is an experienced software engineer and leader with a passion for teaching and mentoring. He has taught on subjects including software testing and led improvement of SDLC practices. He’s always excited to kick the tires on a new language or project.

Read Chris Barile's Full Bio

The opinions expressed on this website are those of each author, not of the author's employer or All Things Open/We Love Open Source.

Want to contribute your open source content?

Contribute to We ❤️ Open Source

Help educate our community by contributing a blog post, tutorial, or how-to.

We're hosting two world-class events in 2026!

Join us for All Things AI, March 23-24 and for All Things Open, October 18-20.

Open Source Meetups

We host some of the most active open source meetups in the U.S. Get more info and RSVP to an upcoming event.