flutter-automated-testing
Goal
Generates, configures, and debugs automated tests for Flutter applications, encompassing unit, widget, integration, and plugin testing. Analyzes architectural components (such as MVVM layers) to produce isolated, mock-driven tests and end-to-end device tests. Assumes a standard Flutter project structure, existing business logic, and familiarity with Dart testing paradigms.
Instructions
- Determine Test Type (Decision Logic)
Evaluate the user's target code to determine the appropriate testing strategy using the following decision tree:
-
If verifying a single function, method, ViewModel, or Repository: Implement a Unit Test (Proceed to Step 2).
-
If verifying a single widget's UI, layout, or interaction: Implement a Widget Test (Proceed to Step 3).
-
If verifying complete app behavior, routing, or performance on a device: Implement an Integration Test (Proceed to Step 4).
-
If verifying platform-specific native code (MethodChannels): Implement a Plugin Test (Proceed to Step 5).
STOP AND ASK THE USER: "Which specific class, widget, or flow are we testing today? Please provide the relevant source code if you haven't already."
- Implement Unit Tests (Logic & Architecture)
Unit tests verify logic without rendering UI. They must reside in the test/ directory and end with _test.dart .
- For ViewModels (UI Layer Logic): Fake the repository dependencies. Do not rely on Flutter UI libraries.
import 'package:test/test.dart'; // Import your ViewModel and Fakes here
void main() { group('HomeViewModel tests', () { test('Load bookings successfully', () { final viewModel = HomeViewModel( bookingRepository: FakeBookingRepository()..createBooking(kBooking), userRepository: FakeUserRepository(), );
expect(viewModel.bookings.isNotEmpty, true);
});
}); }
- For Repositories (Data Layer Logic): Fake the API clients or local database services.
import 'package:test/test.dart'; // Import your Repository and Fakes here
void main() { group('BookingRepositoryRemote tests', () { late BookingRepository bookingRepository; late FakeApiClient fakeApiClient;
setUp(() {
fakeApiClient = FakeApiClient();
bookingRepository = BookingRepositoryRemote(apiClient: fakeApiClient);
});
test('should get booking', () async {
final result = await bookingRepository.getBooking(0);
final booking = result.asOk.value;
expect(booking, kBooking);
});
}); }
- Implement Widget Tests (UI Components)
Widget tests verify UI rendering and interaction. They must reside in the test/ directory and use the flutter_test package.
-
Use WidgetTester to build the widget.
-
Use Finder to locate elements (find.text() , find.byKey() , find.byWidget() ).
-
Use Matcher to verify existence (findsOneWidget , findsNothing , findsNWidgets ).
import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart';
void main() { testWidgets('HomeScreen displays title and handles tap', (WidgetTester tester) async { // 1. Setup Fakes and ViewModel final bookingRepository = FakeBookingRepository()..createBooking(kBooking); final viewModel = HomeViewModel( bookingRepository: bookingRepository, userRepository: FakeUserRepository(), );
// 2. Build the Widget tree
await tester.pumpWidget(
MaterialApp(
home: HomeScreen(viewModel: viewModel),
),
);
// 3. Finders
final titleFinder = find.text('Home');
final buttonFinder = find.byKey(const Key('increment_button'));
// 4. Assertions
expect(titleFinder, findsOneWidget);
// 5. Interactions
await tester.tap(buttonFinder);
await tester.pumpAndSettle(); // Wait for animations/state updates to finish
expect(find.text('1'), findsOneWidget);
}); }
- Implement Integration Tests (End-to-End)
Integration tests run on real devices or emulators. They must reside in the integration_test/ directory.
-
Ensure integration_test is in dev_dependencies in pubspec.yaml .
-
Initialize IntegrationTestWidgetsFlutterBinding .
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' as app;
void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('End-to-End App Test', () { testWidgets('Full flow: tap FAB and verify counter', (WidgetTester tester) async { // Load the full app app.main(); await tester.pumpAndSettle();
// Verify initial state
expect(find.text('0'), findsOneWidget);
// Find and tap the FAB
final fab = find.byKey(const ValueKey('increment'));
await tester.tap(fab);
// Trigger a frame
await tester.pumpAndSettle();
// Verify state change
expect(find.text('1'), findsOneWidget);
});
}); }
- Implement Plugin Tests (Native & Dart)
If testing a plugin, tests must cover both Dart and Native communication.
-
Dart Side: Mock the platform channel and call the plugin's public API.
-
Native Side: Instruct the user to write native tests in the respective directories:
-
Android: android/src/test/ (JUnit)
-
iOS/macOS: example/ios/RunnerTests/ (XCTest)
-
Linux/Windows: linux/test/ (GoogleTest)
- Validate and Fix (Feedback Loop)
Provide the user with the exact command to run the generated test:
-
Unit/Widget: flutter test test/your_test_file.dart
-
Integration: flutter test integration_test/your_test_file.dart
STOP AND ASK THE USER: "Please run the test using the command above and paste the output. If the test fails, provide the stack trace so I can analyze the failure and generate a fix."
Constraints
-
Single Source of Truth: Do not duplicate state in tests. Always use fakes or mocks for external dependencies (Repositories, Services) to isolate the unit under test.
-
No Logic in Widgets: When writing widget tests, assume the widget is "dumb". All business logic should be tested via the ViewModel/Controller unit tests.
-
File Naming: All test files MUST end with _test.dart .
-
Pump vs PumpAndSettle: Use tester.pump() for single frame advances. Use tester.pumpAndSettle() strictly when waiting for animations or asynchronous UI updates to complete.
-
Immutability: Treat test data models as immutable. Create new instances for state changes rather than mutating existing mock data.
-
Do not use dart:mirrors : Flutter does not support reflection. Rely on code generation (e.g., build_runner , mockito , mocktail ) for mocking.