Flutter Tester
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:
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: flutter-tester-23description: 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---56# Flutter Tester78## Overview910Test each architectural layer in isolation using Given-When-Then structure. Always test both success and error paths. Never mock providers — override their dependencies instead.1112## Reference Files1314Load the relevant file based on what you're testing:1516| What you're testing | Reference file |17| --- | --- |18| Repository, DAO, Service logic | `references/layer_testing_patterns.md` |19| Widget UI, interactions, dialogs, navigation | `references/widget_testing_guide.md` |20| Riverpod provider state, mutations, lifecycle | `references/riverpod_testing_guide.md` |2122## Core Principles2324### 1. Layer Isolation2526Test each layer against its own mocked dependencies:2728| Layer | What to test | What to mock |29| --- | --- | --- |30| **Repository** | Data coordination between sources | DAOs, APIs, Logger |31| **DAO** | Database CRUD operations | Use real in-memory DB, mock Logger |32| **Provider** | State management and transitions | Services, Repositories |33| **Service** | Business logic and workflows | Repositories, Network clients |34| **Widget** | UI behaviour and interactions | Provider dependencies (via overrides) |3536### 2. Given-When-Then Structure3738```dart39test('Given valid data, When fetchUsers called, Then returns user list', () async {40 // Arrange (Given)41 when(mockDAO.fetchAll()).thenAnswer((_) async => expectedUsers);4243 // Act (When)44 final result = await repository.fetchUsers();4546 // Assert (Then)47 expect(result, equals(expectedUsers));48 verify(mockDAO.fetchAll()).called(1);49});50```5152### 3. Test Organisation5354```dart55group('UserRepository', () {56 group('fetchUsers', () {57 setUp(() { /* init mocks, register with GetIt */ });58 tearDown(() => GetIt.I.reset()); // Always reset GetIt5960 test('Given success ... When ... Then ...', () { });61 test('Given error ... When ... Then ...', () { });62 });63});64```6566## Standard Test Setup6768### Generate Mocks6970```dart71@GenerateMocks([IUserDAO, IUserAPI, ILogger])72void main() { ... }73```7475Run `dart run build_runner build` after modifying `@GenerateMocks`.7677### Register with GetIt7879```dart80setUp(() {81 mockDAO = MockIUserDAO();82 mockLogger = MockILogger();83 GetIt.I84 ..registerSingleton<IUserDAO>(mockDAO)85 ..registerSingleton<ILogger>(mockLogger);86});8788tearDown(() => GetIt.I.reset()); // Critical — always reset89```9091### Fakes vs Mocks9293- **Fakes** (`class FakeLogger extends ILogger`) — silent stubs; use when you don't need to verify calls94- **Mocks** (`MockILogger`) — use when you need `when()`, `verify()`, or `thenThrow()`9596## Quick Reference9798| Scenario | Key pattern |99| --- | --- |100| Test a repository | Mock DAO + API → inject into repository constructor |101| Test a DAO | `FakeDatabase` or `openInMemoryDatabase()` in setUp, delete table in tearDown |102| Test a Riverpod provider | `createContainer(overrides: [serviceProvider.overrideWith(...)])` |103| Test a widget | Set screen size, use `find.byKey()`, call `pumpAndSettle()` |104| Test a loading state | Use `Completer`, `pump()` to assert loading, complete, `pump()` again |105| Test platform-specific UI | `debugDefaultTargetPlatformOverride = TargetPlatform.iOS` — reset after |106| Test GoRouter navigation | `FakeGoRouter` + `MockGoRouterProvider` |107108## Running Tests109110```bash111flutter test --coverage # All tests with coverage112flutter test test/path/to/test.dart # Specific file113flutter test --plain-name "Given valid data" # Filter by name114genhtml coverage/lcov.info -o coverage/html # Generate HTML coverage report115# Prefix any command with `fvm` if using Flutter Version Manager116```117118## Common Mistakes119120| Mistake | Fix |121| --- | --- |122| Mocking a provider directly | Override its dependencies: `provider.overrideWith(...)` |123| Missing `GetIt.I.reset()` in `tearDown` | Tests pollute each other — always reset |124| `await Future.delayed()` in tests | Use `await tester.pumpAndSettle()` or `Completer` instead |125| Finding widgets by text string | Use `find.byKey(const Key('name'))` — stable across text changes |126| No screen size in widget tests | Add `tester.view.physicalSize = const Size(1000, 1000)` |127| Not resetting `debugDefaultTargetPlatformOverride` | Set to `null` at the end of the test |128| `tearDown()` without a lambda | Write `tearDown(() async { ... })` not `tearDown() async { ... }` |129130## Test Checklist131132**Setup & Mocking:**133134- [ ] Dependencies mocked (not providers)135- [ ] SharedPreferences mocked if used136- [ ] `GetIt.I.reset()` in `tearDown`137- [ ] Streams closed in `tearDown`138- [ ] Controllers disposed in `tearDown`139140**Widget Tests:**141142- [ ] Keys added to source widgets and used in `find.byKey()`143- [ ] Screen size set (`physicalSize` + `devicePixelRatio`)144- [ ] Platform overrides reset (`debugDefaultTargetPlatformOverride = null`)145- [ ] Navigation verified if applicable146147**Test Coverage:**148149- [ ] Success and failure paths covered150- [ ] Edge cases tested (null, empty, max values)151- [ ] Loading and error states tested152- [ ] Async handled correctly (no `Future.delayed`)153154**Code Quality:**155156- [ ] Given-When-Then naming used157- [ ] `verify()` or `verifyNever()` where appropriate158- [ ] Tests are isolated and deterministic159160---161> Converted and distributed by [TomeVault](https://tomevault.io/claim/harishwarrior) — claim your Tome and manage your conversions.162<!-- tomevault:4.0:skill_md:2026-04-11 -->