Skip to main content
RapidDev - Software Development Agency
firebase-tutorial

How to Test Firestore Rules Locally

To test Firestore security rules locally, install the Firebase Emulator Suite, start the Firestore emulator with firebase emulators:start, and connect your app or test scripts to the local emulator instead of production. Use the Rules Playground in the Emulator UI at localhost:4000 for quick manual tests, or write automated test suites using the @firebase/rules-unit-testing library with assertSucceeds() and assertFails() to verify that your rules allow and deny access correctly.

What you'll learn

  • How to set up and start the Firebase Emulator Suite for Firestore
  • How to use the Rules Playground in the Emulator UI for quick tests
  • How to write automated rules tests with @firebase/rules-unit-testing
  • How to connect your app to the local Firestore emulator during development
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate8 min read15-20 minFirebase CLI v13+, Node.js 18+, @firebase/rules-unit-testing v3+March 2026RapidDev Engineering Team
TL;DR

To test Firestore security rules locally, install the Firebase Emulator Suite, start the Firestore emulator with firebase emulators:start, and connect your app or test scripts to the local emulator instead of production. Use the Rules Playground in the Emulator UI at localhost:4000 for quick manual tests, or write automated test suites using the @firebase/rules-unit-testing library with assertSucceeds() and assertFails() to verify that your rules allow and deny access correctly.

Testing Firestore Security Rules with the Local Emulator

Testing security rules against production Firestore is slow, risky, and burns through free quota. The Firebase Emulator Suite lets you run Firestore locally with your rules file, test access patterns instantly, and iterate on rules without deploying. This tutorial covers setting up the emulator, using the Rules Playground for interactive testing, writing automated test suites, and connecting your development app to the local emulator.

Prerequisites

  • Firebase CLI installed (npm install -g firebase-tools)
  • A Firebase project initialized with firebase init (select Firestore and Emulators)
  • A firestore.rules file in your project root
  • Java JDK 11+ installed (required by the Firestore emulator)

Step-by-step guide

1

Initialize the emulator suite in your project

Run firebase init emulators and select the Firestore emulator. The CLI adds emulator configuration to your firebase.json including the port numbers. The default Firestore emulator port is 8080 and the Emulator UI runs on port 4000. You can customize these ports in firebase.json if they conflict with other services.

typescript
1# Initialize emulators (select Firestore when prompted)
2firebase init emulators
3
4# Your firebase.json will include:
5# {
6# "emulators": {
7# "firestore": { "port": 8080 },
8# "ui": { "enabled": true, "port": 4000 }
9# }
10# }

Expected result: firebase.json is updated with emulator configuration. The Firestore emulator is ready to start.

2

Start the emulators and open the Rules Playground

Start all configured emulators with firebase emulators:start. The CLI loads your firestore.rules file and starts the Firestore emulator on port 8080 and the Emulator UI on port 4000. Open localhost:4000 in your browser to access the Emulator UI which includes a Firestore data viewer, Auth user manager, and the Rules Playground for interactive rule testing.

typescript
1# Start all emulators
2firebase emulators:start
3
4# Or start only the Firestore emulator
5firebase emulators:start --only firestore
6
7# With data persistence between restarts
8firebase emulators:start --import=./emulator-data --export-on-exit=./emulator-data

Expected result: The terminal shows the Firestore emulator running on port 8080 and the Emulator UI is accessible at http://localhost:4000.

3

Test rules interactively with the Rules Playground

In the Emulator UI at localhost:4000, navigate to Firestore and click the Rules Playground tab. The playground lets you simulate read and write requests against your rules without writing any code. Select the operation type (get, list, create, update, delete), enter the document path, optionally set an authenticated user context, and provide request data for writes. The playground shows whether the request was allowed or denied and highlights the specific rule that matched.

Expected result: The Rules Playground shows ALLOW or DENY for each simulated request, with the matching rule highlighted in your rules file.

4

Install the rules-unit-testing library for automated tests

Install @firebase/rules-unit-testing as a dev dependency. This library provides helper functions to create test contexts with or without authentication, and assertion functions to verify that operations succeed or fail as expected. It connects directly to the running Firestore emulator.

typescript
1# Install the testing library
2npm install --save-dev @firebase/rules-unit-testing
3
4# Also install a test runner if you don't have one
5npm install --save-dev vitest

Expected result: The @firebase/rules-unit-testing package is installed and ready to use in your test files.

5

Write automated tests for your Firestore rules

Create a test file that initializes the testing environment, sets up test contexts for authenticated and unauthenticated users, and uses assertSucceeds() and assertFails() to verify rule behavior. Each test should check a specific access pattern: can an authenticated user read their own profile? Can they read someone else's profile? Can an unauthenticated user create a post?

typescript
1// tests/firestore-rules.test.ts
2import {
3 initializeTestEnvironment,
4 assertSucceeds,
5 assertFails,
6 RulesTestEnvironment
7} from '@firebase/rules-unit-testing';
8import { doc, getDoc, setDoc, deleteDoc } from 'firebase/firestore';
9import { readFileSync } from 'fs';
10
11let testEnv: RulesTestEnvironment;
12
13beforeAll(async () => {
14 testEnv = await initializeTestEnvironment({
15 projectId: 'test-project',
16 firestore: {
17 rules: readFileSync('firestore.rules', 'utf8'),
18 host: '127.0.0.1',
19 port: 8080
20 }
21 });
22});
23
24afterEach(async () => {
25 await testEnv.clearFirestore();
26});
27
28afterAll(async () => {
29 await testEnv.cleanup();
30});
31
32test('authenticated user can read their own profile', async () => {
33 const alice = testEnv.authenticatedContext('alice');
34 const db = alice.firestore();
35 await assertSucceeds(getDoc(doc(db, 'users', 'alice')));
36});
37
38test('user cannot read another user profile', async () => {
39 const alice = testEnv.authenticatedContext('alice');
40 const db = alice.firestore();
41 await assertFails(getDoc(doc(db, 'users', 'bob')));
42});
43
44test('unauthenticated user cannot create a post', async () => {
45 const unauth = testEnv.unauthenticatedContext();
46 const db = unauth.firestore();
47 await assertFails(
48 setDoc(doc(db, 'posts', 'post-1'), { title: 'Test', authorId: 'anon' })
49 );
50});

Expected result: Running the test suite with vitest confirms that your rules correctly allow and deny access for each test case.

6

Connect your development app to the Firestore emulator

During development, connect your app to the local Firestore emulator instead of production. Call connectFirestoreEmulator() after initializing Firestore. This redirects all Firestore operations to the local emulator. Only do this in development — never in production. A common pattern is to check an environment variable to decide whether to connect to the emulator.

typescript
1import { getFirestore, connectFirestoreEmulator } from 'firebase/firestore';
2import { getAuth, connectAuthEmulator } from 'firebase/auth';
3import { initializeApp } from 'firebase/app';
4
5const app = initializeApp(firebaseConfig);
6const db = getFirestore(app);
7const auth = getAuth(app);
8
9// Connect to emulators in development only
10if (import.meta.env.DEV) {
11 connectFirestoreEmulator(db, '127.0.0.1', 8080);
12 connectAuthEmulator(auth, 'http://127.0.0.1:9099');
13 console.log('Connected to Firebase emulators');
14}

Expected result: Your development app reads and writes to the local Firestore emulator. Changes appear in the Emulator UI at localhost:4000.

Complete working example

firestore-rules.test.ts
1// Complete Firestore rules test suite
2// Tests user profiles, posts, and admin access patterns
3
4import {
5 initializeTestEnvironment,
6 assertSucceeds,
7 assertFails,
8 RulesTestEnvironment
9} from '@firebase/rules-unit-testing';
10import { doc, getDoc, setDoc, updateDoc, deleteDoc } from 'firebase/firestore';
11import { readFileSync } from 'fs';
12
13let testEnv: RulesTestEnvironment;
14
15beforeAll(async () => {
16 testEnv = await initializeTestEnvironment({
17 projectId: 'rules-test',
18 firestore: {
19 rules: readFileSync('firestore.rules', 'utf8'),
20 host: '127.0.0.1',
21 port: 8080
22 }
23 });
24});
25
26afterEach(async () => {
27 await testEnv.clearFirestore();
28});
29
30afterAll(async () => {
31 await testEnv.cleanup();
32});
33
34// --- User profile rules ---
35
36test('user can read own profile', async () => {
37 const ctx = testEnv.authenticatedContext('user1');
38 await assertSucceeds(getDoc(doc(ctx.firestore(), 'users', 'user1')));
39});
40
41test('user cannot read other profiles', async () => {
42 const ctx = testEnv.authenticatedContext('user1');
43 await assertFails(getDoc(doc(ctx.firestore(), 'users', 'user2')));
44});
45
46test('user can create own profile with member role', async () => {
47 const ctx = testEnv.authenticatedContext('user1');
48 await assertSucceeds(
49 setDoc(doc(ctx.firestore(), 'users', 'user1'), {
50 email: 'user1@test.com',
51 displayName: 'User One',
52 role: 'member'
53 })
54 );
55});
56
57test('user cannot create profile with admin role', async () => {
58 const ctx = testEnv.authenticatedContext('user1');
59 await assertFails(
60 setDoc(doc(ctx.firestore(), 'users', 'user1'), {
61 email: 'user1@test.com',
62 displayName: 'User One',
63 role: 'admin'
64 })
65 );
66});
67
68// --- Unauthenticated access ---
69
70test('unauthenticated user cannot read profiles', async () => {
71 const ctx = testEnv.unauthenticatedContext();
72 await assertFails(getDoc(doc(ctx.firestore(), 'users', 'user1')));
73});
74
75test('unauthenticated user cannot write posts', async () => {
76 const ctx = testEnv.unauthenticatedContext();
77 await assertFails(
78 setDoc(doc(ctx.firestore(), 'posts', 'post1'), {
79 title: 'Hack',
80 authorId: 'anon'
81 })
82 );
83});

Common mistakes when testing Firestore Rules Locally

Why it's a problem: Forgetting to start the Firestore emulator before running rules tests, causing connection refused errors

How to avoid: Always run firebase emulators:start in a separate terminal before running your test suite. Add a script like "test:rules": "firebase emulators:exec 'vitest run tests/firestore-rules.test.ts'" to run them together.

Why it's a problem: Not calling testEnv.clearFirestore() between tests, causing test data from one test to affect another

How to avoid: Add testEnv.clearFirestore() in an afterEach block to reset the emulator data between every test for isolation.

Why it's a problem: Using localhost instead of 127.0.0.1 for the emulator host, causing connection failures on systems with IPv6 enabled

How to avoid: Always use 127.0.0.1 as the emulator host in both your test configuration and your app's connectFirestoreEmulator call.

Best practices

  • Run rules tests as part of your CI/CD pipeline using firebase emulators:exec to start emulators, run tests, and shut down automatically
  • Test both positive cases (should succeed) and negative cases (should fail) for every rule
  • Use testEnv.clearFirestore() in afterEach to ensure test isolation
  • Keep your firestore.rules file in version control and review rule changes in pull requests
  • Test edge cases like empty fields, missing auth, and boundary values for data validation rules
  • Use the Rules Playground in the Emulator UI for quick manual checks while developing rules
  • Test with the Auth emulator running so request.auth is properly populated in rules

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

Show me how to test Firestore security rules locally using the Firebase Emulator Suite and @firebase/rules-unit-testing. Include setting up the test environment, testing authenticated and unauthenticated access, and running tests with vitest.

Firebase Prompt

Create a complete Firestore security rules test suite using @firebase/rules-unit-testing with the Firebase Emulator Suite. Include tests for user profile access, post creation with auth, unauthenticated denial, and data validation rules. Use TypeScript and vitest as the test runner.

Frequently asked questions

Do I need Java installed to run the Firestore emulator?

Yes. The Firestore emulator requires Java JDK 11 or higher. Install it from adoptium.net or via your package manager. The Firebase CLI will prompt you if Java is missing.

Can I test rules without starting the full emulator suite?

You can use the Rules Playground in the Firebase Console (production) for quick manual tests, but this counts toward your free-tier quota. For local testing, the emulator is required.

How do I test rules that depend on existing document data?

Use testEnv.withSecurityRulesDisabled() to seed data before your test. This bypasses rules to create the initial state, then your actual test runs with rules enabled against that data.

Do emulator rules hot-reload when I change the firestore.rules file?

Yes. The emulator watches your firestore.rules file and automatically reloads rules when the file changes. You do not need to restart the emulator after editing rules.

Can I run rules tests in CI/CD without a Firebase project?

Yes. The emulator does not require a real Firebase project. Use any string as the projectId in initializeTestEnvironment. The tests run entirely locally against the emulator.

How do I test custom claims in security rules?

Use testEnv.authenticatedContext('uid', { role: 'admin' }) to create a test context with custom claims. The second argument becomes request.auth.token in your rules.

Can RapidDev help set up a testing pipeline for Firestore security rules?

Yes. RapidDev can implement automated security rules testing with the Firebase Emulator Suite, integrate it into your CI/CD pipeline, and ensure comprehensive coverage of your access control patterns.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.