Disclosure: App369 publishes this guide and sells a fixed-price Firebase security rules audit.
Key takeaways
- Test Firebase Security Rules by trying to break them: another user's data, another tenant's data, and a user changing their own role. Write those attempts as unit tests so they run on every rules change.
- With @firebase/rules-unit-testing you create a test environment with initializeTestEnvironment, act as users with authenticatedContext and unauthenticatedContext, and check results with assertSucceeds and assertFails.
- Seed test data inside withSecurityRulesDisabled, otherwise your own rules block the setup. Rules are skipped only inside that callback.
- Use a demo- project ID so test code cannot reach production resources. The Firestore emulator clears its data on shutdown, so each run starts clean.
- firebase emulators:exec starts the emulators, runs your test command and shuts them down, which suits CI. Make the rules tests a required check on any pull request that touches rules files.
- The Emulator UI Requests tab shows how each request was evaluated against your rules, so you can see which rule allowed or denied it.
Pricing an app? Try the free app cost calculator or get a range for your project in the instant quote.
The only reliable way to know your Firebase Security Rules work is to try to break them, automatically, every time they change. Reading rules and clicking around the app misses the cases that matter: another user’s data, another tenant’s data, and a user giving themselves a higher role. The Firebase Emulator Suite and the @firebase/rules-unit-testing library let you write those attempts as ordinary unit tests that run in seconds and never touch production.
This is the technique behind App369’s fixed-price Firebase Security Rules Audit, and the kind of test Simon relies on for the rules in Workspace369, his production Flutter and Firebase SaaS. If you want the rules themselves explained first, start with the Firebase Security Rules guide.
Step 1: Install and start the emulators
You need Node.js, a recent Firebase CLI and a Java runtime for the Firestore emulator. The install and configure docs list the minimum versions, but they can lag behind the CLI, so if the emulator refuses to start, install a current long-term-support JDK and read the CLI’s error message. Then, in your project:
firebase init emulators
firebase emulators:start --only firestore
firebase init emulators lets you pick emulators and downloads their binaries. By default the Firestore emulator listens on port 8080 and the Emulator UI on port 4000. Make sure firebase.json points at your rules file, for example "firestore": { "rules": "firestore.rules" }, plus an emulators block if you want non-default ports. The Firestore emulator docs also recommend demo project IDs (prefixed demo-): code can’t accidentally reach production resources, and no real Firebase project is needed.
Step 2: Set up the test library
npm install --save-dev @firebase/rules-unit-testing firebase
The current library (6.x at the time of writing) is a peer of the modular Firebase JS SDK: version 6 needs firebase 13 and Node.js 24.12 or newer, while version 5 works with firebase 12 and Node.js 20 or newer. Match the two, or npm warns about the peer dependency. You test through RulesTestEnvironment, created with initializeTestEnvironment. The official unit-test guide documents these pieces:
initializeTestEnvironment({ projectId, firestore: { rules } })creates the environment from your rules text.testEnv.authenticatedContext(uid, tokenOptions)acts as a signed-in user;testEnv.unauthenticatedContext()acts as a signed-out client.testEnv.withSecurityRulesDisabled(callback)runs setup code that bypasses rules, so you can seed data.assertSucceeds(promise)andassertFails(promise)check whether rules allowed or denied an operation.testEnv.clearFirestore()clears data between tests, andtestEnv.cleanup()releases resources when you finish.
Step 3: Write a first test file
Say your rules let a user read their own profile, create it only as a plain member, and never change their own role:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{uid} {
allow read: if request.auth != null && request.auth.uid == uid;
allow create: if request.auth != null && request.auth.uid == uid
&& request.resource.data.role == 'member';
allow update: if request.auth != null && request.auth.uid == uid
&& request.resource.data.role == resource.data.role;
}
}
}
A Jest-style test file in TypeScript:
import { readFileSync } from 'fs';
import {
initializeTestEnvironment,
assertFails,
assertSucceeds,
RulesTestEnvironment,
} from '@firebase/rules-unit-testing';
import { doc, getDoc, setDoc, updateDoc } from 'firebase/firestore';
let testEnv: RulesTestEnvironment;
beforeAll(async () => {
testEnv = await initializeTestEnvironment({
projectId: 'demo-rules-test',
firestore: {
rules: readFileSync('firestore.rules', 'utf8'),
host: '127.0.0.1',
port: 8080,
},
});
});
afterAll(async () => {
await testEnv.cleanup();
});
beforeEach(async () => {
await testEnv.clearFirestore();
// Seed data with rules switched off
await testEnv.withSecurityRulesDisabled(async (ctx) => {
await setDoc(doc(ctx.firestore(), 'users/alice'), { role: 'member' });
});
});
test('a user can read their own profile', async () => {
const alice = testEnv.authenticatedContext('alice');
await assertSucceeds(getDoc(doc(alice.firestore(), 'users/alice')));
});
test('a user cannot read someone else\'s profile', async () => {
const bob = testEnv.authenticatedContext('bob');
await assertFails(getDoc(doc(bob.firestore(), 'users/alice')));
});
test('a signed-out client cannot read anything', async () => {
const anon = testEnv.unauthenticatedContext();
await assertFails(getDoc(doc(anon.firestore(), 'users/alice')));
});
test('a user cannot promote themselves to admin', async () => {
const alice = testEnv.authenticatedContext('alice');
await assertFails(updateDoc(doc(alice.firestore(), 'users/alice'), { role: 'admin' }));
});
Run it while the emulator is up, with your usual test runner. The seed step matters: tests that depend on existing documents must create them with withSecurityRulesDisabled, otherwise your own rules block the setup.
Step 4: Decide what to test
Passing tests for the happy path prove little. Write the attacks:
- Cross-user reads and writes. User B reading, updating and deleting user A’s documents, in every collection that holds user data.
- Cross-tenant access. If you have workspaces, teams or organizations, a member of tenant 1 must fail to read or write tenant 2’s documents, including sub-collections. Seed two tenants and test both directions.
- Role escalation. A user writing their own
role,isAdminorplanfield on create and on update. Also test adding themselves to another tenant’s member list. - Field-level validation. Creates with missing or extra fields, wrong types, oversized strings, and fields the client should never set, such as server-owned timestamps or counters.
- Signed-out access. Every path with
unauthenticatedContext(). - Storage paths. The same user and tenant checks against Storage rules. Pass
storage: { rules }toinitializeTestEnvironmentand usectx.storage()in the tests. - Queries, not just single reads. A rule that allows reading one document can still reject a list query that isn’t constrained to what the rule permits. Test the exact query your app runs.
For each tenant-isolation rule, write one test that should succeed and at least one that should fail. If you can’t make a failing test fail, you have found a hole.
Step 5: Run it in CI
firebase emulators:exec starts the emulators, runs your command, and shuts them down when the command ends. The emulator docs describe it as the way to run scripted tests, and it suits continuous integration:
firebase emulators:exec --only firestore "npm test"
The Firestore emulator clears its data when it shuts down, so every CI run starts clean. Cache the emulator download (the CLI keeps the emulator JARs in ~/.cache/firebase/emulators/) between CI runs if your provider supports it, and add the Java runtime to the CI image. Make the rules test a required check for any pull request that touches firestore.rules, storage.rules or the tests.
Step 6: Debug with the request monitor
When a test fails and you don’t know which rule caused it, run firebase emulators:start, point a manual client at it, and open the Emulator UI at http://localhost:4000. In the Firestore section, the Requests tab shows each request and traces how your rules were evaluated, which line allowed or denied it. The Firestore emulator docs describe this request monitoring. It is the fastest way to find out that a rule you thought applied never matched.
When to get help
Before you write tests, a quick first pass helps: paste your rules into the free Firebase Security Rules checker to spot public paths, test-mode rules and sign-in-only checks. It runs in your browser.
If you don’t have the time to build the model of who should access what, or you have already found a hole and want the fixes reviewed, App369’s fixed-price Firebase Security Rules Audit + Fix PR is $950 and delivered in 5 business days. It covers Firestore and Storage rules plus up to 15 Cloud Functions in one Firebase project, and you get a written role and tenant access model, an emulator test suite for cross-user, cross-tenant and role-escalation attempts, a severity-ranked report and a pull request with fixes that passes the suite. It runs on the emulator or test data only, and it is a technical review, not a compliance certificate.
Want this done at a fixed price?
Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.
Frequently asked questions
- How do I test Firestore security rules locally?
- Run the Firestore emulator with firebase emulators:start --only firestore, then write tests with @firebase/rules-unit-testing. Create a test environment with initializeTestEnvironment, act as users with authenticatedContext, and check results with assertSucceeds and assertFails.
- Can I test Firebase Storage rules the same way?
- Yes. Pass a storage option with your rules to initializeTestEnvironment, start the Storage emulator, and perform uploads and downloads through the context's storage() client. The same assertions apply.
- How do I run Firebase rules tests in CI?
- Use firebase emulators:exec to start the emulators, run your test command and stop them, for example firebase emulators:exec --only firestore "npm test". The CI machine needs Node.js, the Firebase CLI and a Java runtime. Firebase calls emulators:exec generally more appropriate for CI workflows.
- How do I add test data when my rules would block it?
- Call testEnv.withSecurityRulesDisabled with an async callback and write documents through the context's firestore() client. Rules are skipped inside that callback only.
- Does passing rules tests mean my app is secure?
- No. Tests show your rules behave as you described them, so they are only as good as the cases you wrote. They do not cover Cloud Functions logic, leaked credentials or anything outside Firebase.
- Which version of @firebase/rules-unit-testing should I install?
- Match it to your firebase package. As of October 2026, version 6 needs firebase 13 and Node.js 24.12 or newer, and version 5 works with firebase 12 and Node.js 20 or newer. Check the peer dependency warning from npm.
Sources
- Firebase docs, Build unit tests for Security Rules
- Firebase docs, Install, configure and integrate the Local Emulator Suite
- Firebase docs, Connect your app to the Cloud Firestore Emulator
- Firebase docs, Connect your app to the Cloud Storage emulator
- Firebase docs, Test your Cloud Firestore Security Rules
- Firebase docs, Prototype and test (emulators:exec)
Related Resources
Related guides
Agent Tool Optimization: How to Get Your App Picked in Google AI Mode, Gemini, ChatGPT and Claude
Agent Tool Optimization (ATO): how to get your app picked inside Google AI Mode, Gemini, ChatGPT and Claude, with an 11-point checklist. Checked Nov 1, 2026.
Read moreBuild a CRM with AI, Hire a Developer, or Buy One? (2026)
Three ways to get a CRM in 2026: buy one, build it yourself with Claude Code or Cursor, or hire a developer. Real prices and timelines, each with its source.
Read more