Unit tests are great for logic, and widget tests are perfect for individual components, but neither tells you if your app actually works when a user taps a button and navigates through three screens. That’s where integration testing comes in. In this guide, I’m going to walk you through writing flutter integration tests step by step so you can stop worrying about critical regressions in your production builds.
In my experience, the biggest hurdle isn’t writing the test code—it’s the setup. Whether you are migrating from a Detox React Native testing tutorial approach or starting fresh with Dart, the mental model is the same: simulate a real user interaction in a real environment.
Prerequisites
- Flutter SDK installed and configured on your path.
- A physical device or an emulator/simulator running.
- Basic familiarity with
flutter_testandasync/awaitin Dart. - A Flutter project with at least two screens to test navigation.
Step 1: Adding the Integration Test Dependency
Integration tests are now supported by a first-party package. You don’t need external third-party frameworks for basic E2E flow. First, add the integration_test package to your pubspec.yaml under dev_dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
integration_test:
sdk: flutter
After adding this, run flutter pub get in your terminal to fetch the package.
Step 2: Creating the Test Directory Structure
Integration tests live outside the standard test/ folder because they are compiled into the app itself. You need to create a new top-level directory called integration_test/. Inside this folder, create a file named app_test.dart.
Your directory should look like this:
/my_flutter_app/integration_test/app_test.dart
Step 3: Writing Your First Integration Test
Now we get to the core of writing flutter integration tests step by step. We need to initialize the integration test driver and then write a test that interacts with the UI. Here is a complete example of a login flow test:
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() {
// 1. Initialize the integration test driver
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('End-to-End Login Flow', () {
testWidgets('User can login successfully', (WidgetTester tester) async {
// 2. Start the app
app.main();
await tester.pumpAndSettle();
// 3. Find elements and enter text
await tester.enterText(find.byKey(const Key('email_field')), 'test@ajmani.dev');
await tester.enterText(find.byKey(const Key('password_field')), 'password123');
// 4. Tap the login button
await tester.tap(find.byType(ElevatedButton));
// 5. Wait for the transition to the home screen
await tester.pumpAndSettle();
// 6. Verify the home screen is visible
expect(find.text('Welcome Home'), findsOneWidget);
});
});
}
As shown in the image below, the key is to use pumpAndSettle(). Unlike widget tests, integration tests deal with real animations and network delays, so you must wait for the UI to stabilize.
Step 4: Running the Tests
You cannot run integration tests using the standard flutter test command. You must use the flutter drive command or the newer flutter test integration_test command.
Run this in your terminal:
flutter test integration_test/app_test.dart
If you are targeting a specific device, use flutter test integration_test/app_test.dart -d <DEVICE_ID>. I highly recommend testing on a physical device to catch performance stutters that emulators often miss.
Pro Tips for Robust Integration Tests
- Use Keys: Avoid finding widgets by text (which changes with localization). Use
Key('login_button')for stability. - Mocking APIs: For consistent results, I suggest creating a “test flavor” of your app that uses a mock API client instead of hitting production servers.
- Avoid Hard-Coded Delays: Never use
Future.delayed. Always rely ontester.pumpAndSettle()ortester.pump(duration). - CI/CD Integration: Integration tests are slow. Run them on a separate pipeline trigger rather than on every single commit. If you want to scale this, check out my mobile app test automation course for engineers.
Troubleshooting Common Issues
| Issue | Likely Cause | Solution |
|---|---|---|
| Test times out | Network request taking too long | Increase timeout or mock the API response. |
| Widget not found | Animation still running | Add another await tester.pumpAndSettle(). |
| App crashes on start | Missing IntegrationTestWidgetsFlutterBinding |
Ensure the binding is initialized at the top of main(). |
What’s Next?
Now that you’ve mastered writing flutter integration tests step by step, the next step is to implement a testing pyramid. Balance your project with 70% unit tests, 20% widget tests, and 10% integration tests. This ensures your pipeline remains fast while still providing high confidence in your releases.