1. Unit Testing Basics
Setting Up Test Dependencies
dev_dependencies:
flutter_test:
sdk: flutter
mockito: ^5.4.0
build_runner: ^2.4.0
Basic Test Structure
void main() {
group('User Service Tests', () {
late UserService userService;
late MockHttpClient mockClient;
setUp(() {
mockClient = MockHttpClient();
userService = UserService(client: mockClient);
});
test('fetchUser returns user when successful', () async {
// Arrange
when(mockClient.get(any))
.thenAnswer((_) async => Response('{"id": 1}', 200));
// Act
final user = await userService.fetchUser(1);
// Assert
expect(user.id, equals(1));
});
});
}
2. Widget Testing
Pump and Settle
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame
await tester.pumpWidget(MyApp());
// Verify initial state
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify the counter has incremented
expect(find.text('1'), findsOneWidget);
expect(find.text('0'), findsNothing);
});
Finding Widgets
// By key
find.byKey(Key('my-widget'))
// By type
find.byType(ElevatedButton)
// By text
find.text('Click me')
// By icon
find.byIcon(Icons.add)
// By semantic label
find.bySemanticsLabel('Add item')
3. Integration Testing
Setting Up Integration Tests
// 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('tap on the floating action button',
(WidgetTester tester) async {
app.main();
await tester.pumpAndSettle();
// Verify the initial state
expect(find.text('0'), findsOneWidget);
// Act: Tap the increment button
await tester.tap(find.byType(FloatingActionButton));
await tester.pumpAndSettle();
// Assert: Verify the counter is incremented
expect(find.text('1'), findsOneWidget);
});
});
}
4. Mocking and Fakes
Creating a Mock with Mockito
// Generate mock class
@GenerateMocks([HttpClient])
import 'package:mockito/annotations.dart';
// Using the mock
class MockHttpClient extends Mock implements HttpClient {}
Creating a Fake
class FakeUserRepository implements UserRepository {
final List _users = [];
@override
Future<User?> getUser(int id) async {
return _users.firstWhere((user) => user.id == id);
}
@override
Future saveUser(User user) async {
_users.add(user);
}
}
5. Test Coverage
Running Tests with Coverage
# Run tests with coverage
flutter test --coverage
# Generate HTML report (requires lcov)
genhtml coverage/lcov.info -o coverage/html
# Open coverage report
open coverage/html/index.html
Excluding Files from Coverage
// coverage:ignore-file
import 'package:flutter/material.dart';
// coverage:ignore-start
void main() {
runApp(MyApp());
}
// coverage:ignore-end
6. Golden Tests
Creating a Golden Test
testWidgets('MyWidget matches golden file',
(WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: MyWidget(),
),
);
await expectLater(
find.byType(MyWidget),
matchesGoldenFile('my_widget.png'),
);
});
Platform-Specific Golden Tests
testWidgets('MyWidget matches golden file',
(WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: MyWidget(),
),
);
await expectLater(
find.byType(MyWidget),
matchesGoldenFile('my_widget_${Platform.operatingSystem}.png'),
);
});
7. Best Practices and Tips
- Always follow the Arrange-Act-Assert pattern in tests
- Use meaningful test descriptions that explain the behavior being tested
- Keep tests focused and test one thing at a time
- Use setup and teardown to avoid code duplication
- Don't test implementation details, test behavior
- Use test doubles (mocks, fakes, stubs) appropriately
- Maintain test independence - tests should not depend on each other
- Consider using test fixtures for complex data setup
- Remember to test error cases and edge cases
- Keep your tests maintainable and readable