← Articles

Testing Flutter apps with Firebase emulators

By Ann Tech · 6 June 2026

Running integration tests against a live Firebase project is slow, expensive, and leaves test data in production. Firebase emulators solve all three problems — they run locally, cost nothing, and reset between test runs.

Install and configure emulators

npm install -g firebase-tools
firebase login
firebase init emulators

Select: Firestore, Authentication, Functions (and any others you use).

firebase.json:

{
  "emulators": {
    "auth": { "port": 9099 },
    "firestore": { "port": 8080 },
    "functions": { "port": 5001 },
    "ui": { "enabled": true, "port": 4000 }
  }
}
# Start emulators
firebase emulators:start

The Emulator UI at localhost:4000 shows real-time data, auth users, and function logs.

Connecting Flutter to emulators

Future<void> connectToEmulators() async {
  const host = 'localhost'; // Use '10.0.2.2' for Android emulator

  // Firestore
  FirebaseFirestore.instance.useFirestoreEmulator(host, 8080);

  // Auth
  await FirebaseAuth.instance.useAuthEmulator(host, 9099);

  // Functions
  FirebaseFunctions.instance.useFunctionsEmulator(host, 5001);

  // Storage
  await FirebaseStorage.instance.useStorageEmulator(host, 9199);
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);

  // Connect to emulators in debug/test mode only
  if (kDebugMode || const bool.fromEnvironment('USE_EMULATORS')) {
    await connectToEmulators();
  }

  runApp(const MyApp());
}

Integration tests with emulators

// integration_test/firestore_test.dart
void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  setUpAll(() async {
    await Firebase.initializeApp();
    FirebaseFirestore.instance.useFirestoreEmulator('localhost', 8080);
    await FirebaseAuth.instance.useAuthEmulator('localhost', 9099);
  });

  setUp(() async {
    // Clear emulator data between tests
    await FirebaseFirestore.instance.clearPersistence();
    // Sign out before each test
    await FirebaseAuth.instance.signOut();
  });

  testWidgets('user can create and see an order', (tester) async {
    // Create a test user in the Auth emulator
    final credential = await FirebaseAuth.instance
        .createUserWithEmailAndPassword(
          email: '[email protected]',
          password: 'password123',
        );

    // Seed test data
    await FirebaseFirestore.instance
        .collection('products')
        .doc('product-1')
        .set({'name': 'Test Product', 'price': 9.99});

    // Launch app
    app.main();
    await tester.pumpAndSettle();

    // Interact with the app
    await tester.tap(find.byKey(const Key('product-1')));
    await tester.pumpAndSettle();
    await tester.tap(find.byKey(const Key('add_to_cart')));
    await tester.pumpAndSettle();
    await tester.tap(find.byKey(const Key('checkout')));
    await tester.pumpAndSettle();

    // Verify the order was created in Firestore
    final orders = await FirebaseFirestore.instance
        .collection('orders')
        .where('userId', isEqualTo: credential.user!.uid)
        .get();
    expect(orders.docs.length, 1);
  });
}

Running tests against emulators in CI

# .github/workflows/integration-tests.yml
jobs:
  integration-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install Firebase CLI
        run: npm install -g firebase-tools

      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.24.0'
          cache: true

      - uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Start emulators and run tests
        run: |
          firebase emulators:exec             --project demo-test             "flutter test integration_test/ -d emulator-5554               --dart-define=USE_EMULATORS=true"

Seeding test data

// test/helpers/firebase_seed.dart
Future<void> seedProducts(FirebaseFirestore db) async {
  final batch = db.batch();
  for (final product in testProducts) {
    batch.set(
      db.collection('products').doc(product.id),
      product.toJson(),
    );
  }
  await batch.commit();
}

Future<void> seedSecurityRules(String projectId) async {
  // Optionally load rules from firestore.rules file
}

Common pitfalls

Using localhost on Android emulator. Android emulators can't reach the host machine's localhost. Use 10.0.2.2 instead: useFirestoreEmulator('10.0.2.2', 8080).

Not clearing emulator data between tests. If one test creates data that a later test doesn't expect, tests fail intermittently. Always call FirebaseFirestore.instance.clearPersistence() in setUp.

Connecting to emulators in production. Gate emulator connections behind kDebugMode or a --dart-define flag. Shipping code that connects to localhost:8080 to production means users see empty data or connection errors.

Sign in to like, dislike, or report.

Testing Flutter apps with Firebase emulators — ANN Tech