Setting Up the Firebase Emulator Suite
Ticket: SCRUM-127 | Status: Approved ✅
What This Task Was About
When writing integration tests for services like wordService or authService, those services call Firebase. Without an emulator, tests would hit the real Firestore database — creating junk data, potentially costing money at scale, and being slow and unreliable.
The Firebase Emulator Suite runs a fake Firebase locally on your machine. Tests run fast, never touch production, and you can wipe data between tests.
Acceptance criteria:
firebase emulators:startruns without errorsfirebase.jsonhas emulators configured- A
setup.tsfile connects to the emulators before tests run - Can explain why we use emulators instead of real Firebase
Why Your Tests Hit Real Firebase (Without an Emulator)
The chain of dependencies looks like this:
Your test → imports wordService.ts → which imports db from config.ts → which calls initializeApp() pointing at your real Firebase project
Specifically, wordService.ts line 3:
import { db } from "../firebase/config";
And wordService.ts line 38:
const docRef = await addDoc(collection(db, "words"), ...);
That db object is a live connection to your real Firestore database (vocabpal-learning). When a test calls createWord(...), it actually writes a document to production. Same for authService.ts — getAuth() is connected to your real Firebase Auth.
Mental model: Think of db and auth like a phone already dialed to a number. When you call initializeApp(), the phone is dialed to Firebase's servers. When you call connectFirestoreEmulator(), you're re-routing that call to a local fake number (localhost:8080) before any test runs. All the same Firebase SDK functions work identically — your service code does not change at all.
What I Did
Step 1: Install Firebase CLI
curl -sL firebase.tools | bash
Step 2: Initialise Firebase project
firebase init
firebase login # if not already logged in
Step 3: Initialise emulators
firebase init emulators
Select Firestore and Authentication emulators. This creates an emulators section in firebase.json:
{
"emulators": {
"auth": {
"port": 9099
},
"firestore": {
"port": 8080
},
"ui": {
"enabled": true
}
}
}
Step 4: Start the emulator
firebase emulators:start
The Emulator UI loads at http://localhost:4000. You can inspect data, clear collections, and watch in real time.
Step 5: Create setup.ts to redirect calls to the emulator
A Vitest setup file is a TypeScript file that runs before any test. It's the right place to redirect Firebase SDK calls away from the real servers.
// shared/src/services/__tests__/setup.ts
import { connectFirestoreEmulator } from 'firebase/firestore';
import { connectAuthEmulator } from 'firebase/auth';
import { db, auth } from '../../firebase/config';
// Tell Firebase SDK: use localhost instead of the real servers
connectFirestoreEmulator(db, 'localhost', 8080);
connectAuthEmulator(auth, 'http://localhost:9099');
Why setup.ts instead of each test file?
You can only call connectFirestoreEmulator() once per Firebase app instance — calling it twice throws an error. Putting it in setup.ts means it runs once before all tests, and every test file automatically uses the emulator.
Why do the two functions have different signatures?
connectFirestoreEmulator(db, 'localhost', 8080) takes separate host and port. connectAuthEmulator(auth, 'http://localhost:9099') takes a full URL. This is an inconsistency in the Firebase SDK API — just how it was designed.
Step 6: Add setupFiles to vitest.config.ts
// shared/vitest.config.ts
export default defineConfig({
test: {
globals: true,
environment: 'node',
setupFiles: ['./src/services/__tests__/setup.ts'], // ← add this
},
});
Vitest's startup sequence:
- Reads
vitest.config.ts - Runs every file listed in
setupFiles←setup.tsruns here - Collects test files
- Runs tests
Mistakes and Fixes
Issue: jest.mock instead of vi.mock
In wordService.test.ts I had:
jest.mock('firebase/firestore');
jest.mock('../../firebase/config');
This project uses Vitest, not Jest. The global is vi, not jest.
Fix:
vi.mock('firebase/firestore');
vi.mock('../../firebase/config');
When globals: true is set in vitest.config.ts, Vitest injects vi globally — same idea as Jest injecting jest globally.
Also cleaned up as part of the fix:
- Added
"types": ["vitest/globals"]toshared/tsconfig.jsonso TypeScript knows aboutvi,describe,it, etc. - Removed
@types/jest,jest, andts-jestfrompackage.json— they were leftover and don't belong in a Vitest project.
Key Concepts
| Concept | What it is |
|---|---|
firebase emulators:start | Launches local Firebase services (Firestore, Auth, etc.) |
firebase.json | Config file declaring which emulators to run and on which ports |
connectFirestoreEmulator(db, 'localhost', 8080) | Redirects the Firestore SDK to use local emulator |
connectAuthEmulator(auth, 'http://localhost:9099') | Same but for Firebase Auth |
setupFiles in vitest config | Files that run once before any test — used to connect to emulators |
Session Notes & Code Review Feedback
Katie Nguyen (2026-02-27):
This is new to me, can you add more learning materials?
Katie Nguyen (2026-03-02):
Learning Materials: Firebase Emulator Suite
Goal: Understand what the Firebase Emulator is, why we use it for testing, and how to set it up so your tests run against a fake Firebase instead of the real one.
Why do we need this?
When you write integration tests for wordService or authService, those services call Firebase. Without an emulator, your tests would hit the real Firestore database — creating junk data, costing money (at scale), and being slow + unreliable. The Firebase Emulator runs a fake Firebase locally on your machine. Tests run fast, never touch production, and you can reset data between tests.
Watch (25-30 mins):
- Firebase Emulator Suite Overview (Firebase official, ~8 min) — search "Firebase Local Emulator Suite overview" on YouTube
- Firebase Emulator Suite Tutorial by Fireship (~12 min) — search "Fireship Firebase Emulator" on YouTube
Read (20-25 mins):
- Firebase Emulator Suite intro — firebase.google.com/docs/emulator-suite
- Connect your app to the emulators — firebase.google.com/docs/emulator-suite/connect_firestore
- Install and configure the emulators — firebase.google.com/docs/emulator-suite/install_and_configure
Katie Nguyen (2026-03-02):
Steps I followed:
- Download Firebase tools:
curl -sL firebase.tools | bash - Initialise Firebase project:
firebase init - (if required):
firebase login firebase emulators:start- Add setup.ts inside tests folder to redirect calls to emulator instead of real Firestore
- Add setup.ts into setupFiles of vitest config so it runs before any test
Katie Nguyen (2026-03-03):
[Code Review Feedback] Answering your question about the vitest setup file
Great question — this step is one of the trickier parts. Let me break it down.
A setup file is just a TypeScript file that runs before any of your tests. Think of it like prep work — you can start services, configure environment variables, or connect to tools your tests will need. You tell vitest about it in the config:
// shared/vitest.config.ts
export default defineConfig({
test: {
globals: true,
environment: 'node',
setupFiles: ['./src/services/__tests__/setup.ts'], // <-- add this
},
});
When your tests run, they import wordService/authService which both call the real Firebase. The setup file is where you redirect those calls to the emulator instead:
// shared/src/services/__tests__/setup.ts
import { connectFirestoreEmulator } from 'firebase/firestore';
import { connectAuthEmulator } from 'firebase/auth';
import { db, auth } from '../../firebase/config';
connectFirestoreEmulator(db, 'localhost', 8080);
connectAuthEmulator(auth, 'http://localhost:9099');
Those port numbers (8080 for Firestore, 9099 for Auth) are the default ports the Firebase Emulator starts on. You can see them in your firebase.json emulator config.
Why connect in setup.ts instead of inside each test file? You only need to call connectFirestoreEmulator() once per Firebase app instance — calling it twice throws an error. By putting it in setup.ts, it runs once before all tests, and every test file automatically uses the emulator.
Reference materials:
- Firebase docs — Connect your app to the emulator: https://firebase.google.com/docs/emulator-suite/connect_firestore
- Vitest docs — setupFiles option: https://vitest.dev/config/#setupfiles
Katie Nguyen (2026-03-03):
[Code Review Feedback] SCRUM-127 — Set up Firebase Emulator
Good work overall — the core deliverables for this ticket are all correct.
What went well:
firebase.json— emulators section is correct, auth on 9099, Firestore on 8080, UI enabled.setup.ts— imports are correct, both emulator connections are in place. The inline comments explaining WHY the two functions have different signatures are excellent — you clearly read the API docs rather than just copying the code.vitest.config.ts—setupFilescorrectly points to yoursetup.ts. Vitest will now run that file before every test.
One issue to fix:
In wordService.test.ts you have jest.mock(...). This project uses Vitest, not Jest. Change to vi.mock(...).
Katie Nguyen (2026-03-03):
[Code Review - APPROVED] SCRUM-127 — Set up Firebase Emulator
vi.mock()— fixed ✅"types": ["vitest/globals"]added toshared/tsconfig.json✅@types/jest,jest,ts-jestremoved frompackage.json✅
All done. SCRUM-127 approved.