Flutter Testing
Testing patterns for Flutter apps with BLoC/Cubit, Clean Architecture, bloc_test, and mocktail.
When to Use
- •Writing BLoC/Cubit, widget, or integration tests
- •Mocking dependencies with mocktail
- •Testing Clean Architecture layers
- •Setting up test coverage or TDD workflow
Test Types
| Type | Purpose | Coverage |
|---|---|---|
| Unit | BLoC/Cubit, repositories | 80%+ |
| Widget | UI components | Key flows |
| Integration | E2E flows | Critical paths |
Dependencies
dev_dependencies:
flutter_test:
sdk: flutter
bloc_test: ^9.1.0
mocktail: ^1.0.0
integration_test:
sdk: flutter
BLoC/Cubit Testing
import 'package:bloc_test/bloc_test.dart';
import 'package:mocktail/mocktail.dart';
class MockRepository extends Mock implements FeatureRepository {}
blocTest<FeatureCubit, FeatureState>(
'emits [loading, loaded] on success',
build: () {
when(() => mockRepo.getData(any()))
.thenAnswer((_) => TaskEither.right(mockData));
return FeatureCubit(mockRepo);
},
act: (cubit) => cubit.loadData('id'),
expect: () => [
const FeatureState(status: Status.loading),
FeatureState(status: Status.loaded, data: mockData),
],
verify: (_) => verify(() => mockRepo.getData('id')).called(1),
);
Details: BLOC_CUBIT_TESTING.md
Widget Testing
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
class MockFeatureCubit extends Mock implements FeatureCubit {}
testWidgets('displays data when loaded', (tester) async {
final mockCubit = MockFeatureCubit();
when(() => mockCubit.state).thenReturn(
FeatureState(status: Status.loaded, data: mockData),
);
await tester.pumpWidget(
MaterialApp(
home: BlocProvider(create: (_) => mockCubit, child: FeaturePage()),
),
);
expect(find.text('Expected Data'), findsOneWidget);
});
testWidgets('handles tap', (tester) async {
await tester.tap(find.byKey(Key('button')));
await tester.pumpAndSettle();
verify(() => mockCubit.loadData()).called(1);
});
Details: WIDGET_TESTING.md
Integration Testing
import 'package:integration_test/integration_test.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('complete user journey', (tester) async {
await tester.pumpWidget(MyApp());
await tester.pumpAndSettle();
await tester.tap(find.byKey(Key('nav_button')));
await tester.pumpAndSettle();
expect(find.text('Success'), findsOneWidget);
});
}
Best Practices
✅ Test behavior not implementation • Mock dependencies only • Cover success AND failure • Use descriptive names • Test state transitions • Use verify() for interactions • Add keys to widgets • Use pumpAndSettle() after async
❌ Don't test private methods • Don't mock the thing being tested • Don't skip error tests • Don't use hard-coded delays
Commands
flutter test # All tests flutter test --coverage # With coverage flutter test test/path/ # Specific path flutter test integration_test/ # Integration tests
Mocking Essentials
// Setup
class MockRepo extends Mock implements FeatureRepo {}
when(() => mock.method(any())).thenAnswer((_) => TaskEither.right(data));
// Verify
verify(() => mock.method('arg')).called(1);
verifyNever(() => mock.method(any()));
// Fallback for custom types
setUpAll(() => registerFallbackValue(FakeFeature()));
References
- •BLoC/Cubit Testing - State management patterns
- •Widget Testing - UI testing and finders
- •Mocking Patterns - Mocktail techniques
- •Repository Testing - Testing repositories and datasources
- •Integration Testing - E2E flows and user journeys
- •Clean Architecture - Layer-by-layer testing strategy
- •Advanced Patterns - Golden tests, performance, custom matchers
Quick Tips
- •Use keys for reliable widget finding
- •
pumpAndSettle()after async operations - •Mock dependencies, not the thing being tested
- •Test both success and failure paths
- •Use
isA<T>().having()for complex assertions