FlutterFlow has three built-in testing tools: Run Mode (fast visual preview, no real backend), Test Mode (real Firestore and auth with your test data), and device deployment via code export. For comprehensive coverage, supplement with Flutter unit and widget tests on exported code and API tests in Postman. Always test against a dedicated test environment — never use production Firestore data.
Testing in FlutterFlow: A Layered Approach
FlutterFlow doesn't have a traditional test runner built in — you can't write automated UI tests inside the visual builder. Instead, testing happens at four layers: (1) Run Mode for instant visual feedback while building, (2) Test Mode for functional testing against real backend data, (3) physical device testing via code export for hardware-specific issues like camera, GPS, and push notifications, and (4) Flutter unit and widget tests written in the exported codebase for regression prevention. Each layer catches different bugs, and skipping any one of them leads to preventable production incidents.
Prerequisites
- A FlutterFlow project with at least a few pages, actions, and Firestore queries built
- A separate Firebase project or Firestore database for test data (or at least a clearly separated collection naming convention)
- Postman or a REST API client installed for testing API integrations
- FlutterFlow Pro for code export (required for Flutter test writing)
Step-by-step guide
Use Run Mode for rapid visual and interaction testing
Use Run Mode for rapid visual and interaction testing
Run Mode (the play button, top-right in FlutterFlow) compiles your app in the browser using FlutterFlow's hosted runner. It renders your actual widgets, fires action flows, and shows real navigation. However, Firestore reads and writes in Run Mode connect to your LIVE Firebase project unless you have a separate test project configured. To test safely in Run Mode, use FlutterFlow's 'Test Data' feature: go to each Firestore collection, click the Test Data tab, and populate sample records. These appear in Run Mode queries without writing to production. Run Mode is best for visual layout checks, widget interactions, navigation flows, and action logic — not for push notifications, camera features, or deep device integrations.
Expected result: Run Mode renders your app with test data visible in lists and queries responding correctly to user interactions.
Set up isolated test data in Firestore
Set up isolated test data in Firestore
Create a dedicated test environment before running Test Mode. Option A (recommended): create a second Firebase project named 'myapp-test' with its own Firestore database. In FlutterFlow Settings → Firebase → switch to the test project credentials for testing. Option B: in your existing project, prefix all test collection names with 'test_' (e.g., 'test_users', 'test_orders') and update your FlutterFlow queries to use the test collections during development. Option C: use Firestore Emulator Suite — run firebase emulators:start locally and point your test builds at localhost. Populate test Firestore with realistic but fake data: multiple user personas (new user, power user, admin), edge case records (empty lists, max-length strings, zero values), and error-triggering states.
Expected result: Test Mode connects to your test Firestore database showing fake data, completely isolated from production users.
Run systematic functional tests in Test Mode
Run systematic functional tests in Test Mode
Test Mode (the flask icon, next to Run Mode) runs against your real Firebase project with real Authentication and Firestore. Create a test checklist before starting. Test each critical user flow: new user registration, login with wrong password (verify error message), profile edit and save, creating and deleting records, all navigation paths, and empty state handling (what shows when a Firestore query returns zero results). Test each Action Flow by triggering it and verifying the Firestore write appears in the Firebase Console. Test conditional visibility — create test data that satisfies each condition and verify the correct elements appear. Log your test results as a simple checklist noting pass, fail, or not-tested for each flow.
Expected result: You have a completed test checklist with all critical user flows marked as pass or fail, with specific bug details for any failures.
Test on physical devices via code export
Test on physical devices via code export
Export your FlutterFlow project (Pro required) and open it in VS Code with Flutter SDK installed. For iOS testing: run flutter run -d <device-id> targeting a connected iPhone. For Android: connect an Android device with USB debugging enabled and run the same command. Physical device testing catches issues that Run Mode and Test Mode cannot: push notification delivery, camera and microphone permissions, GPS accuracy, biometric authentication, network behavior on slow connections, deep link handling, and performance on low-end hardware. Create a device-specific test checklist covering: app launch time, scrolling smoothness in long lists, camera open speed, push notification receipt, and app behavior when network connection drops mid-operation.
Expected result: The app installs and runs on a physical device without crashes, and device-specific features (camera, notifications, biometrics) function correctly.
Write Flutter widget and unit tests in the exported codebase
Write Flutter widget and unit tests in the exported codebase
In your exported Flutter project, navigate to the test/ directory (created automatically). Write unit tests for Custom Functions that contain business logic. For example, test calculateProfileComplete() with various combinations of filled and empty fields, verifying the returned Double. Write widget tests for complex Custom Widgets to verify they render correctly and respond to props. Use flutter_test package (already a dev dependency in Flutter projects). Run tests with flutter test. For regression prevention, add a test for every bug you fix: write a test that reproduces the bug, verify it fails, fix the bug, verify the test passes. Commit both the fix and the test together.
1// test/custom_functions_test.dart2import 'package:flutter_test/flutter_test.dart';3// Import your custom function file45void main() {6 group('calculateProfileComplete', () {7 test('returns 0.0 for completely empty profile', () {8 final result = calculateProfileComplete(9 '', '', '', [], null, '#6366F1',10 );11 expect(result, 0.0);12 });13 test('returns 1.0 for fully complete profile', () {14 final result = calculateProfileComplete(15 'John Doe',16 'https://example.com/avatar.png',17 'Flutter developer',18 ['Tech', 'Design'],19 {'website': 'https://example.com'},20 '#FF5733',21 );22 expect(result, 1.0);23 });24 test('returns 0.5 for half-complete profile', () {25 final result = calculateProfileComplete(26 'Jane', 'https://example.com/img.png', '',27 [], null, '#6366F1',28 );29 expect(result, closeTo(0.33, 0.01));30 });31 });32}Expected result: flutter test runs all tests and shows a green checkmark for each passing test case.
Complete working example
1// Flutter integration test for API calls2// Tests that Custom Actions calling external APIs work correctly3// Run with: flutter test integration_test/api_test.dart --device-id <device-id>45import 'package:flutter_test/flutter_test.dart';6import 'package:integration_test/integration_test.dart';7import 'package:your_app/main.dart' as app;89void main() {10 IntegrationTestWidgetsFlutterBinding.ensureInitialized();1112 group('API Integration Tests', () {13 testWidgets('Search returns results for valid query', (14 WidgetTester tester,15 ) async {16 app.main();17 await tester.pumpAndSettle();1819 // Navigate to search page20 await tester.tap(find.byKey(const Key('search_nav_item')));21 await tester.pumpAndSettle();2223 // Enter a search query24 await tester.enterText(25 find.byKey(const Key('search_text_field')),26 'flutter',27 );28 await tester.pumpAndSettle(const Duration(seconds: 2));2930 // Verify results appear31 expect(find.byKey(const Key('search_results_list')), findsOneWidget);32 expect(find.byType(ListTile), findsWidgets);33 });3435 testWidgets('Empty search shows no-results state', (36 WidgetTester tester,37 ) async {38 app.main();39 await tester.pumpAndSettle();4041 await tester.tap(find.byKey(const Key('search_nav_item')));42 await tester.pumpAndSettle();4344 await tester.enterText(45 find.byKey(const Key('search_text_field')),46 'xyzxyzxyznotarealanything',47 );48 await tester.pumpAndSettle(const Duration(seconds: 2));4950 expect(find.text('No results found'), findsOneWidget);51 });5253 testWidgets('Profile save updates Firestore correctly', (54 WidgetTester tester,55 ) async {56 app.main();57 await tester.pumpAndSettle();5859 await tester.tap(find.byKey(const Key('profile_nav_item')));60 await tester.pumpAndSettle();6162 await tester.tap(find.byKey(const Key('edit_profile_button')));63 await tester.pumpAndSettle();6465 await tester.enterText(66 find.byKey(const Key('display_name_field')),67 'Test User ${DateTime.now().millisecondsSinceEpoch}',68 );69 await tester.tap(find.byKey(const Key('save_profile_button')));70 await tester.pumpAndSettle(const Duration(seconds: 3));7172 expect(find.text('Profile saved'), findsOneWidget);73 });74 });75}Common mistakes when creating Custom Tests in FlutterFlow
Why it's a problem: Testing directly against production Firestore data instead of a dedicated test environment
How to avoid: Always test against a separate Firebase project or clearly isolated test collections. Use FlutterFlow's environment switching or maintain separate firebase-config files for test and production builds.
Why it's a problem: Only testing the happy path and never edge cases or error states
How to avoid: For each feature, explicitly test: empty inputs, maximum-length inputs, network timeout behavior, what happens when a required Firestore document doesn't exist, and what the UI shows when a query returns zero results.
Why it's a problem: Relying solely on Run Mode without ever testing on a physical device
How to avoid: Export and run on physical devices as part of your pre-release checklist for every meaningful feature. At minimum, test on one iOS device and one Android device before each production deployment.
Best practices
- Maintain a written test checklist for your app's critical user flows and check it off before every production release — no matter how small the change.
- Never test with production user credentials or real payment methods — create dedicated test accounts and use Stripe's test card numbers.
- Write a test for every bug you find and fix, so the same bug cannot silently reappear in a future change.
- Keep your test Firestore populated with diverse test data: a brand-new user, an active power user, a user with edge case data (very long strings, emoji in names, zero balances), and an admin user.
- Test your app at different network speeds using Chrome DevTools' network throttling when in Run Mode — a feature that works at 100Mbps may time out on a 3G connection.
- Automate repetitive regression tests using Flutter integration tests run in a CI/CD pipeline (GitHub Actions + firebase test lab) so tests run on every code change.
- Document known limitations of each test layer in your team's notes — everyone should understand what Run Mode can and cannot test versus physical device testing.
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I built a FlutterFlow app and exported the Flutter code. I want to write unit tests for my Custom Functions. I have a function called 'calculateShippingCost' that accepts: orderTotal (double), userTier (String: 'free', 'premium', 'vip'), and itemCount (int). Free tier: $5.99 flat. Premium: free over $50, else $2.99. VIP: always free. Write me Flutter unit tests covering all tier combinations and the edge case of exactly $50 total for premium.
I'm testing my FlutterFlow app in Test Mode and my Firestore queries show empty results even though I added test data. I see the test data in the Firebase Console under my test collection. What are the most common reasons Firestore queries return empty in FlutterFlow Test Mode, and how do I debug which part of the query is filtering out the records?
Frequently asked questions
Can I write automated UI tests directly inside FlutterFlow without exporting?
No. FlutterFlow does not have a built-in automated test runner. All automated testing must be done on the exported Flutter codebase using flutter test or flutter drive. Within FlutterFlow itself, testing is manual — you interact with the app in Run Mode or Test Mode and verify behavior visually.
Does FlutterFlow's Test Mode use my real Firebase quota?
Yes. Test Mode connects to your actual Firebase project. Reads and writes in Test Mode count against your Firestore read/write quotas and billing. This is why using a separate test Firebase project is recommended — it keeps test activity isolated from production billing and prevents accidental data contamination.
How do I test push notifications in FlutterFlow?
Push notifications cannot be tested in Run Mode or Test Mode — they require a real device with the app installed as a native build. Export your project, run flutter run on a physical device, then trigger the notification from your admin panel or directly from the Firebase Console using the FCM test notification tool.
What is the fastest way to reset test data between test sessions?
Create a Cloud Function triggered by an HTTP request that deletes all documents from your test collections and re-creates them from a seed data set. Call this endpoint from Postman at the start of each test session. This gives you a clean, consistent starting state in under 10 seconds.
How do I test my app's behavior when there is no internet connection?
On a physical device, enable Airplane Mode and then interact with the app. FlutterFlow apps using Firestore benefit from offline persistence (enabled by default) so some operations will work offline and sync when connectivity returns. Test that your app shows appropriate error messages for operations that genuinely require connectivity (like sending a new push notification).
Should I test on iOS and Android separately, or is one sufficient?
Test on both. Flutter produces near-identical behavior on iOS and Android for most features, but platform-specific differences do exist: permission dialogs differ, push notification handling behaves differently, keyboard behavior varies, and some UI rendering details (shadow, font metrics, scroll physics) differ between platforms. At minimum, smoke-test on both before each release.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation