Integration Testing with the integration_test Package
Integration Testing with the integration_test Package
Integration tests verify your Flutter application as a whole by running it on a real device or emulator. Unlike unit tests (which test individual functions in isolation) or widget tests (which run in a simulated environment), integration tests exercise the full app stack — navigation, state management, animations, and real platform channels — giving you the highest confidence that your app works correctly for your users.
integration_test package is the official Flutter testing package for end-to-end tests. It replaced the legacy flutter_driver package and integrates directly with the flutter_test API, so all the matchers and finders you already know work unchanged.Setting Up the integration_test Package
Add the package to your pubspec.yaml under dev_dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
integration_test:
sdk: flutter
Integration test files live in a top-level integration_test/ directory (not inside test/). Create a companion driver file at test_driver/integration_test.dart when you need to run on Firebase Test Lab or with flutter drive:
// test_driver/integration_test.dart
import 'package:integration_test/integration_test_driver.dart';
Future<void> main() => integrationDriver();
IntegrationTestWidgetsFlutterBinding
Every integration test file must call IntegrationTestWidgetsFlutterBinding.ensureInitialized() before any test group. This replaces the default test binding with one that communicates with the host machine (or CI system) so that results, screenshots, and performance timelines can be reported externally.
// integration_test/app_test.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('Counter App Integration Tests', () {
testWidgets('increments counter and displays updated value',
(WidgetTester tester) async {
// Boot the real app
app.main();
await tester.pumpAndSettle();
// Verify the initial state
expect(find.text('0'), findsOneWidget);
// Drive the UI: tap the FAB
await tester.tap(find.byTooltip('Increment'));
await tester.pumpAndSettle();
// Assert the result
expect(find.text('1'), findsOneWidget);
});
});
}
app.main() (your real main() entry point) inside the test body, not at the top level. This ensures each test starts from a clean app state when the test suite contains multiple tests.Driving Multi-Screen Flows
The real power of integration tests is verifying navigation and data flow across screens. Use tester.tap(), tester.enterText(), and tester.pumpAndSettle() to simulate a full user journey:
testWidgets('user can log in and see the home screen',
(WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();
// We should land on the Login screen
expect(find.text('Sign In'), findsOneWidget);
// Fill in credentials
await tester.enterText(
find.byKey(const Key('emailField')),
'user@example.com',
);
await tester.enterText(
find.byKey(const Key('passwordField')),
'secret123',
);
// Submit the form
await tester.tap(find.byKey(const Key('signInButton')));
await tester.pumpAndSettle();
// Assert we navigated to the Home screen
expect(find.text('Welcome back!'), findsOneWidget);
expect(find.byType(HomeScreen), findsOneWidget);
});
Running Integration Tests
Integration tests are executed with flutter test (preferred) or flutter drive:
- On a connected device or emulator:
flutter test integration_test/app_test.dart - All integration tests:
flutter test integration_test/ - With flutter drive (for Firebase Test Lab):
flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart
Taking Screenshots During Tests
The integration_test package can capture screenshots at any point in a test. This is invaluable for visual regression detection and CI reporting:
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('captures login screen screenshot', (WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();
// Take a screenshot before interaction
await binding.takeScreenshot('login_screen');
await tester.tap(find.byKey(const Key('signInButton')));
await tester.pumpAndSettle();
// Take a screenshot after navigation
await binding.takeScreenshot('home_screen');
});
Best Practices
- Use
ValueKeyorKeyon interactive widgets so finders are robust and don't break when labels change. - Use
tester.pumpAndSettle()instead oftester.pump()after actions that trigger animations or async operations; it waits until all frames settle. - Seed the database or mock network calls at the app level (via dependency injection or environment flags) to make integration tests deterministic.
- Keep each integration test focused on one user story — a single login flow, a single purchase flow. Avoid omnibus tests that verify everything.
Summary
Integration tests with the integration_test package let you drive a real Flutter app on a device, interact with actual widgets across multiple screens, and assert end-to-end behaviour. The key steps are: add the package as a dev dependency, call IntegrationTestWidgetsFlutterBinding.ensureInitialized(), launch your real app.main(), use the familiar flutter_test finders and matchers, and run with flutter test integration_test/. Reserve integration tests for your most critical user flows and let unit and widget tests cover the rest.