--- name: "flutter-testing-apps" description: "Implements unit, widget, and integration tests for a Flutter app. Use when ensuring code quality and preventing regressions through automated testing." metadata: model: "models/gemini-3.1-pro-preview" last_modified: "Thu, 12 Mar 2026 22:22:10 GMT" --- # Testing Flutter Applications ## Contents - [Core Testing Strategies](#core-testing-strategies) - [Architectural Testing Guidelines](#architectural-testing-guidelines) - [Plugin Testing Guidelines](#plugin-testing-guidelines) - [Workflows](#workflows) - [Examples](#examples) ## Core Testing Strategies Balance your testing suite across three main categories to optimize for confidence, maintenance cost, dependencies, and execution speed. ### Unit Tests Use unit tests to verify the correctness of a single function, method, or class under various conditions. - Mock all external dependencies. - Do not involve disk I/O, screen rendering, or user actions from outside the test process. - Execute using the `test` or `flutter_test` package. ### Widget Tests Use widget tests (component tests) to ensure a single widget's UI looks and interacts as expected. - Provide the appropriate widget lifecycle context using `WidgetTester`. - Use `Finder` classes to locate widgets and `Matcher` constants to verify their existence and state. - Test views and UI interactions without spinning up the full application. ### Integration Tests Use integration tests (end-to-end or GUI testing) to validate how individual pieces of an app work together and to capture performance metrics on real devices. - Add the `integration_test` package as a dependency. - Run on physical devices, OS emulators, or Firebase Test Lab. - Prioritize integration tests for routing, dependency injection, and critical user flows. ## Architectural Testing Guidelines Design your application for observability and testability. Ensure all components can be tested both in isolation and together. - **ViewModels**: Write unit tests for every ViewModel class. Test the UI logic without relying on Flutter libraries or testing frameworks. - **Repositories & Services**: Write unit tests for every service and repository. Mock the underlying data sources (e.g., HTTP clients, local databases). - **Views**: Write widget tests for all views. Pass faked or mocked ViewModels and Repositories into the widget tree to isolate the UI. - **Fakes over Mocks**: Prefer creating `Fake` implementations of your repositories (e.g., `FakeUserRepository`) over using mocking libraries when testing ViewModels and Views to ensure well-defined inputs and outputs. ## Plugin Testing Guidelines When testing plugins, combine Dart tests with native platform tests to ensure full coverage across the method channel. - **Dart Tests**: Use Dart unit and widget tests for the Dart-facing API. Mock the platform channel to validate Dart logic. - **Native Unit Tests**: Implement native unit tests for isolated platform logic. - Android: Configure JUnit tests in `android/src/test/`. - iOS/macOS: Configure XCTest tests in `example/ios/RunnerTests/` and `example/macos/RunnerTests/`. - Linux/Windows: Configure GoogleTest tests in `linux/test/` and `windows/test/`. - **Native UI Tests**: Use Espresso (Android) or XCUITest (iOS) if the plugin requires native UI interactions. - **Integration Tests**: Write at least one integration test for each platform channel call to verify Dart-to-Native communication. - **End-to-End Fallback**: If integration tests cannot cover a flow (e.g., mocking device state), synthesize calls to the method channel entry point using native unit tests, and test the Dart public API using Dart unit tests. ## Workflows ### Workflow: Implementing a Component Test Suite Copy and track this checklist when implementing tests for a new architectural feature. - [ ] **Task Progress** - [ ] Create `Fake` implementations for any new Repositories or Services. - [ ] Write Unit Tests for the Repository (mocking the API/Database). - [ ] Write Unit Tests for the ViewModel (injecting the Fake Repositories). - [ ] Write Widget Tests for the View (injecting the ViewModel and Fake Repositories). - [ ] Write an Integration Test for the critical path involving this feature. - [ ] Run validator -> review coverage -> fix missing edge cases. ### Workflow: Running Integration Tests Follow conditional logic based on the target platform when executing integration tests. 1. **If testing on Mobile (Local)**: - Connect the Android/iOS device or emulator. - Run: `flutter test integration_test/app_test.dart` 2. **If testing on Web**: - Install and launch ChromeDriver: `chromedriver --port=4444` - Run: `flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart -d chrome` 3. **If testing on Linux (CI System)**: - Invoke an X server using `xvfb-run` to provide a display environment. - Run: `xvfb-run flutter test integration_test/app_test.dart -d linux` 4. **If testing via Firebase Test Lab**: - Build the Android test APKs: `flutter build apk --debug` and `./gradlew app:assembleAndroidTest` - Upload the App APK and Test APK to the Firebase Console. ## Examples ### Example: ViewModel Unit Test Demonstrates testing a ViewModel using a Fake Repository. ```dart import 'package:flutter_test/flutter_test.dart'; void main() { group('HomeViewModel tests', () { test('Load bookings successfully', () { // Inject fake dependencies final viewModel = HomeViewModel( bookingRepository: FakeBookingRepository()..createBooking(kBooking), userRepository: FakeUserRepository(), ); // Verify state expect(viewModel.bookings.isNotEmpty, true); }); }); } ``` ### Example: View Widget Test Demonstrates testing a View by pumping a localized widget tree with fake dependencies. ```dart import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { group('HomeScreen tests', () { late HomeViewModel viewModel; late FakeBookingRepository bookingRepository; setUp(() { bookingRepository = FakeBookingRepository()..createBooking(kBooking); viewModel = HomeViewModel( bookingRepository: bookingRepository, userRepository: FakeUserRepository(), ); }); testWidgets('renders bookings list', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: HomeScreen(viewModel: viewModel), ), ); // Verify UI state expect(find.byType(ListView), findsOneWidget); expect(find.text('Booking 1'), findsOneWidget); }); }); } ``` ### Example: Integration Test Demonstrates a full end-to-end test using the `integration_test` package. ```dart import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:my_app/main.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('end-to-end test', () { testWidgets('tap on the floating action button, verify counter', (tester) async { // Load app widget await tester.pumpWidget(const MyApp()); // Verify initial state expect(find.text('0'), findsOneWidget); // Find and tap the button final fab = find.byKey(const ValueKey('increment')); await tester.tap(fab); // Trigger a frame to allow animations/state to settle await tester.pumpAndSettle(); // Verify updated state expect(find.text('1'), findsOneWidget); }); }); } ```