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
Initialize the emulator suite in your project
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.
1# Initialize emulators (select Firestore when prompted)2firebase init emulators34# 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.
Start the emulators and open the Rules Playground
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.
1# Start all emulators2firebase emulators:start34# Or start only the Firestore emulator5firebase emulators:start --only firestore67# With data persistence between restarts8firebase emulators:start --import=./emulator-data --export-on-exit=./emulator-dataExpected result: The terminal shows the Firestore emulator running on port 8080 and the Emulator UI is accessible at http://localhost:4000.
Test rules interactively with the Rules Playground
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.
Install the rules-unit-testing library for automated tests
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.
1# Install the testing library2npm install --save-dev @firebase/rules-unit-testing34# Also install a test runner if you don't have one5npm install --save-dev vitestExpected result: The @firebase/rules-unit-testing package is installed and ready to use in your test files.
Write automated tests for your Firestore rules
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?
1// tests/firestore-rules.test.ts2import {3 initializeTestEnvironment,4 assertSucceeds,5 assertFails,6 RulesTestEnvironment7} from '@firebase/rules-unit-testing';8import { doc, getDoc, setDoc, deleteDoc } from 'firebase/firestore';9import { readFileSync } from 'fs';1011let testEnv: RulesTestEnvironment;1213beforeAll(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: 808020 }21 });22});2324afterEach(async () => {25 await testEnv.clearFirestore();26});2728afterAll(async () => {29 await testEnv.cleanup();30});3132test('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});3738test('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});4344test('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.
Connect your development app to the Firestore emulator
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.
1import { getFirestore, connectFirestoreEmulator } from 'firebase/firestore';2import { getAuth, connectAuthEmulator } from 'firebase/auth';3import { initializeApp } from 'firebase/app';45const app = initializeApp(firebaseConfig);6const db = getFirestore(app);7const auth = getAuth(app);89// Connect to emulators in development only10if (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
1// Complete Firestore rules test suite2// Tests user profiles, posts, and admin access patterns34import {5 initializeTestEnvironment,6 assertSucceeds,7 assertFails,8 RulesTestEnvironment9} from '@firebase/rules-unit-testing';10import { doc, getDoc, setDoc, updateDoc, deleteDoc } from 'firebase/firestore';11import { readFileSync } from 'fs';1213let testEnv: RulesTestEnvironment;1415beforeAll(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: 808022 }23 });24});2526afterEach(async () => {27 await testEnv.clearFirestore();28});2930afterAll(async () => {31 await testEnv.cleanup();32});3334// --- User profile rules ---3536test('user can read own profile', async () => {37 const ctx = testEnv.authenticatedContext('user1');38 await assertSucceeds(getDoc(doc(ctx.firestore(), 'users', 'user1')));39});4041test('user cannot read other profiles', async () => {42 const ctx = testEnv.authenticatedContext('user1');43 await assertFails(getDoc(doc(ctx.firestore(), 'users', 'user2')));44});4546test('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});5657test('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});6768// --- Unauthenticated access ---6970test('unauthenticated user cannot read profiles', async () => {71 const ctx = testEnv.unauthenticatedContext();72 await assertFails(getDoc(doc(ctx.firestore(), 'users', 'user1')));73});7475test('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.
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.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation