Testing is critical for Flutter apps. Flutter provides three levels of testing: unit, widget, and integration.
Unit tests
Test a single function, method, or class in isolation:
# pubspec.yaml
dev_dependencies:
flutter_test:
sdk: flutter// lib/calculator.dart
class Calculator {
int add(int a, int b) => a + b;
int divide(int a, int b) {
if (b == 0) throw ArgumentError('Cannot divide by zero');
return a ~/ b;
}
}// test/calculator_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/calculator.dart';
void main() {
late Calculator calculator;
setUp(() {
calculator = Calculator();
});
test('add returns the sum of two numbers', () {
expect(calculator.add(2, 3), 5);
});
test('divide throws on zero', () {
expect(() => calculator.divide(10, 0), throwsArgumentError);
});
group('Calculator', () {
test('add handles negative numbers', () {
expect(calculator.add(-1, 1), 0);
});
test('divide returns integer result', () {
expect(calculator.divide(10, 3), 3);
});
});
}Run: flutter test test/calculator_test.dart
Widget tests
Test a single widget’s rendering and interaction:
// test/counter_widget_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/counter_widget.dart';
void main() {
testWidgets('Counter increments when button pressed', (tester) async {
// Render the widget
await tester.pumpWidget(MaterialApp(home: CounterWidget()));
// Verify initial state
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the button
await tester.tap(find.byType(FloatingActionButton));
await tester.pump(); // rebuild after state change
// Verify updated state
expect(find.text('1'), findsOneWidget);
expect(find.text('0'), findsNothing);
});
}Common widget test finders
find.text('Hello') // by text
find.byType(Text) // by widget type
find.byIcon(Icons.add) // by icon
find.byKey(ValueKey('myKey')) // by key
find.byWidget(myWidget) // by widget instance
find.ancestor(
of: find.text('Item'),
matching: find.byType(ListTile),
) // by ancestorCommon widget test actions
await tester.tap(find.byIcon(Icons.add)); // tap
await tester.enterText(find.byType(TextField), 'hello'); // type text
await tester.scrollUntilVisible(find.text('Item 50'), 100); // scroll
await tester.drag(find.byType(Dismissible), Offset(-500, 0)); // swipe
await tester.longPress(find.text('Long press me')); // long press
await tester.pump(); // trigger a rebuild
await tester.pumpAndSettle(); // wait until no more framesMocking with Mocktail
dev_dependencies:
mocktail: ^1.0.0import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
// Create a mock
class MockUserRepository extends Mock implements UserRepository {}
void main() {
late MockUserRepository mockRepo;
setUp(() {
mockRepo = MockUserRepository();
// Register fallback values for any parameters
registerFallbackValue(User(id: '', name: ''));
});
test('fetches users from repository', () async {
// Arrange
final users = [User(id: '1', name: 'Alice')];
when(() => mockRepo.getUsers()).thenAnswer((_) async => users);
// Act
final result = await mockRepo.getUsers();
// Assert
expect(result, users);
verify(() => mockRepo.getUsers()).called(1);
});
}Integration tests
Test the full app running on a device or emulator:
dev_dependencies:
integration_test:
sdk: flutter// 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('end-to-end test', () {
testWidgets('full app flow', (tester) async {
app.main();
await tester.pumpAndSettle();
// Verify home screen loads
expect(find.text('Welcome'), findsOneWidget);
// Navigate to details
await tester.tap(find.text('Details'));
await tester.pumpAndSettle();
// Verify details screen
expect(find.text('Detail Page'), findsOneWidget);
});
});
}Run: flutter test integration_test/app_test.dart
Run all tests
# Run all unit and widget tests
flutter test
# Run with coverage
flutter test --coverage
# Run a specific test file
flutter test test/calculator_test.dart
# Run integration tests on a connected device
flutter test integration_test/app_test.dart