Flutter Tester
Requirements
- Flutter project with
flutter_test dependency
- Works with Riverpod, Mockito, and GetIt
- Run
dart run build_runner build to generate mocks after adding @GenerateMocks annotations
- Compatible with FVM (
fvm flutter test instead of flutter test)
Overview
Test each architectural layer in isolation using Given-When-Then structure. Always test both success and error paths. Never mock providers — override their dependencies instead.
Reference Files
Load the relevant file based on what you're testing:
| What you're testing |
Reference file |
| Repository, DAO, Service logic |
references/layer_testing_patterns.md |
| Widget UI, interactions, dialogs, navigation |
references/widget_testing_guide.md |
| Riverpod provider state, mutations, lifecycle |
references/riverpod_testing_guide.md |
Core Principles
1. Layer Isolation
Test each layer against its own mocked dependencies:
| Layer |
What to test |
What to mock |
| Repository |
Data coordination between sources |
DAOs, APIs, Logger |
| DAO |
Database CRUD operations |
Use real in-memory DB, mock Logger |
| Provider |
State management and transitions |
Services, Repositories |
| Service |
Business logic and workflows |
Repositories, Network clients |
| Widget |
UI behaviour and interactions |
Provider dependencies (via overrides) |
2. Given-When-Then Structure
test('Given valid data, When fetchUsers called, Then returns user list', () async {
// Arrange (Given)
when(mockDAO.fetchAll()).thenAnswer((_) async => expectedUsers);
// Act (When)
final result = await repository.fetchUsers();
// Assert (Then)
expect(result, equals(expectedUsers));
verify(mockDAO.fetchAll()).called(1);
});
3. Test Organisation
group('UserRepository', () {
group('fetchUsers', () {
setUp(() { /* init mocks, register with GetIt */ });
tearDown(() => GetIt.I.reset()); // Always reset GetIt
test('Given success ... When ... Then ...', () { });
test('Given error ... When ... Then ...', () { });
});
});
Standard Test Setup
Generate Mocks
@GenerateMocks([IUserDAO, IUserAPI, ILogger])
void main() { ... }
Run dart run build_runner build after modifying @GenerateMocks.
Register with GetIt
setUp(() {
mockDAO = MockIUserDAO();
mockLogger = MockILogger();
GetIt.I
..registerSingleton<IUserDAO>(mockDAO)
..registerSingleton<ILogger>(mockLogger);
});
tearDown(() => GetIt.I.reset()); // Critical — always reset
Fakes vs Mocks
- Fakes (
class FakeLogger extends ILogger) — silent stubs; use when you don't need to verify calls
- Mocks (
MockILogger) — use when you need when(), verify(), or thenThrow()
Quick Reference
| Scenario |
Key pattern |
| Test a repository |
Mock DAO + API → inject into repository constructor |
| Test a DAO |
FakeDatabase or openInMemoryDatabase() in setUp, delete table in tearDown |
| Test a Riverpod provider |
createContainer(overrides: [serviceProvider.overrideWith(...)]) |
| Test a widget |
Set screen size, use find.byKey(), call pumpAndSettle() |
| Test a loading state |
Use Completer, pump() to assert loading, complete, pump() again |
| Test platform-specific UI |
debugDefaultTargetPlatformOverride = TargetPlatform.iOS — reset after |
| Test GoRouter navigation |
FakeGoRouter + MockGoRouterProvider |
Running Tests
flutter test --coverage # All tests with coverage
flutter test test/path/to/test.dart # Specific file
flutter test --plain-name "Given valid data" # Filter by name
genhtml coverage/lcov.info -o coverage/html # Generate HTML coverage report
# Prefix any command with `fvm` if using Flutter Version Manager
Common Mistakes
| Mistake |
Fix |
| Mocking a provider directly |
Override its dependencies: provider.overrideWith(...) |
Missing GetIt.I.reset() in tearDown |
Tests pollute each other — always reset |
await Future.delayed() in tests |
Use await tester.pumpAndSettle() or Completer instead |
| Finding widgets by text string |
Use find.byKey(const Key('name')) — stable across text changes |
| No screen size in widget tests |
Add tester.view.physicalSize = const Size(1000, 1000) |
Not resetting debugDefaultTargetPlatformOverride |
Set to null at the end of the test |
tearDown() without a lambda |
Write tearDown(() async { ... }) not tearDown() async { ... } |
Test Checklist
Setup & Mocking:
Widget Tests:
Test Coverage:
Code Quality:
1---2name: flutter-tester3description: Use when creating, writing, fixing, or reviewing tests in a Flutter project. Covers unit tests, widget tests, integration tests, Riverpod provider testing, and Mockito mocking. Provides Given-When-Then patterns, layer isolation strategies, and test setup for GetIt, SharedPreferences, and FakeDatabase.4---5
6# Flutter Tester
7
8## Requirements
9
10- Flutter project with `flutter_test` dependency
11- Works with Riverpod, Mockito, and GetIt
12- Run `dart run build_runner build` to generate mocks after adding `@GenerateMocks` annotations
13- Compatible with FVM (`fvm flutter test` instead of `flutter test`)
14
15## Overview
16
17Test each architectural layer in isolation using Given-When-Then structure. Always test both success and error paths. Never mock providers — override their dependencies instead.
18
19## Reference Files
20
21Load the relevant file based on what you're testing:
22
23| What you're testing | Reference file |
24| --- | --- |
25| Repository, DAO, Service logic | `references/layer_testing_patterns.md` |
26| Widget UI, interactions, dialogs, navigation | `references/widget_testing_guide.md` |
27| Riverpod provider state, mutations, lifecycle | `references/riverpod_testing_guide.md` |
28
29## Core Principles
30
31### 1. Layer Isolation
32
33Test each layer against its own mocked dependencies:
34
35| Layer | What to test | What to mock |
36| --- | --- | --- |
37| **Repository** | Data coordination between sources | DAOs, APIs, Logger |
38| **DAO** | Database CRUD operations | Use real in-memory DB, mock Logger |
39| **Provider** | State management and transitions | Services, Repositories |
40| **Service** | Business logic and workflows | Repositories, Network clients |
41| **Widget** | UI behaviour and interactions | Provider dependencies (via overrides) |
42
43### 2. Given-When-Then Structure
44
45```dart
46test('Given valid data, When fetchUsers called, Then returns user list', () async {
47 // Arrange (Given)
48 when(mockDAO.fetchAll()).thenAnswer((_) async => expectedUsers);
49
50 // Act (When)
51 final result = await repository.fetchUsers();
52
53 // Assert (Then)
54 expect(result, equals(expectedUsers));
55 verify(mockDAO.fetchAll()).called(1);
56});
57```
58
59### 3. Test Organisation
60
61```dart
62group('UserRepository', () {
63 group('fetchUsers', () {
64 setUp(() { /* init mocks, register with GetIt */ });
65 tearDown(() => GetIt.I.reset()); // Always reset GetIt
66
67 test('Given success ... When ... Then ...', () { });
68 test('Given error ... When ... Then ...', () { });
69 });
70});
71```
72
73## Standard Test Setup
74
75### Generate Mocks
76
77```dart
78@GenerateMocks([IUserDAO, IUserAPI, ILogger])
79void main() { ... }
80```
81
82Run `dart run build_runner build` after modifying `@GenerateMocks`.
83
84### Register with GetIt
85
86```dart
87setUp(() {
88 mockDAO = MockIUserDAO();
89 mockLogger = MockILogger();
90 GetIt.I
91 ..registerSingleton<IUserDAO>(mockDAO)
92 ..registerSingleton<ILogger>(mockLogger);
93});
94
95tearDown(() => GetIt.I.reset()); // Critical — always reset
96```
97
98### Fakes vs Mocks
99
100- **Fakes** (`class FakeLogger extends ILogger`) — silent stubs; use when you don't need to verify calls
101- **Mocks** (`MockILogger`) — use when you need `when()`, `verify()`, or `thenThrow()`
102
103## Quick Reference
104
105| Scenario | Key pattern |
106| --- | --- |
107| Test a repository | Mock DAO + API → inject into repository constructor |
108| Test a DAO | `FakeDatabase` or `openInMemoryDatabase()` in setUp, delete table in tearDown |
109| Test a Riverpod provider | `createContainer(overrides: [serviceProvider.overrideWith(...)])` |
110| Test a widget | Set screen size, use `find.byKey()`, call `pumpAndSettle()` |
111| Test a loading state | Use `Completer`, `pump()` to assert loading, complete, `pump()` again |
112| Test platform-specific UI | `debugDefaultTargetPlatformOverride = TargetPlatform.iOS` — reset after |
113| Test GoRouter navigation | `FakeGoRouter` + `MockGoRouterProvider` |
114
115## Running Tests
116
117```bash
118flutter test --coverage # All tests with coverage
119flutter test test/path/to/test.dart # Specific file
120flutter test --plain-name "Given valid data" # Filter by name
121genhtml coverage/lcov.info -o coverage/html # Generate HTML coverage report
122# Prefix any command with `fvm` if using Flutter Version Manager
123```
124
125## Common Mistakes
126
127| Mistake | Fix |
128| --- | --- |
129| Mocking a provider directly | Override its dependencies: `provider.overrideWith(...)` |
130| Missing `GetIt.I.reset()` in `tearDown` | Tests pollute each other — always reset |
131| `await Future.delayed()` in tests | Use `await tester.pumpAndSettle()` or `Completer` instead |
132| Finding widgets by text string | Use `find.byKey(const Key('name'))` — stable across text changes |
133| No screen size in widget tests | Add `tester.view.physicalSize = const Size(1000, 1000)` |
134| Not resetting `debugDefaultTargetPlatformOverride` | Set to `null` at the end of the test |
135| `tearDown()` without a lambda | Write `tearDown(() async { ... })` not `tearDown() async { ... }` |
136
137## Test Checklist
138
139**Setup & Mocking:**
140
141- [ ] Dependencies mocked (not providers)
142- [ ] SharedPreferences mocked if used
143- [ ] `GetIt.I.reset()` in `tearDown`
144- [ ] Streams closed in `tearDown`
145- [ ] Controllers disposed in `tearDown`
146
147**Widget Tests:**
148
149- [ ] Keys added to source widgets and used in `find.byKey()`
150- [ ] Screen size set (`physicalSize` + `devicePixelRatio`)
151- [ ] Platform overrides reset (`debugDefaultTargetPlatformOverride = null`)
152- [ ] Navigation verified if applicable
153
154**Test Coverage:**
155
156- [ ] Success and failure paths covered
157- [ ] Edge cases tested (null, empty, max values)
158- [ ] Loading and error states tested
159- [ ] Async handled correctly (no `Future.delayed`)
160
161**Code Quality:**
162
163- [ ] Given-When-Then naming used
164- [ ] `verify()` or `verifyNever()` where appropriate
165- [ ] Tests are isolated and deterministic