Testing Strategy
- Test Pyramid: More unit and widget tests, fewer integration tests. Unit tests are fastest and cheapest.
- Mirror Test Rule: 100% logic and widget coverage. No code without a test.
- Mirror Organization: Test files MUST strictly mirror the
lib/ directory structure and end with _test.dart.
- Coverage Targets: Target 100% logic coverage for
domain and bloc layers.
- Test Independence: Each test MUST be independent. No shared mutable state between tests.
Test Types Overview
| Type |
Scope |
Speed |
Skill |
| Unit |
Single function/class |
Fast |
dart-testing |
| Widget |
Single UI component |
Medium |
flutter-add-widget-test |
| Integration |
Full app / user flow |
Slow |
flutter-add-integration-test |
Pattern-Based Testing
These three patterns cut repetitive test setup, cover all visual states, and keep widget behavior consistent. They're conventions, not packages.
Golden Variant Testing
When a widget has multiple visual states (primary, disabled, hover, error), test all variants in a single structured group() with a Map<String, Widget Function()>:
- Define a
variants map where keys are state names and values are widget builders.
- Each variant MUST be rendered in isolation (fresh
pumpWidget call per variant).
- Use deterministic golden file naming:
goldens/<component>.<variant>.png.
- Set a consistent
surfaceSize via tester.view.physicalSize to avoid flaky pixel diffs.
- When to use: Design system components, widgets with distinct modes/types.
- When NOT to use: Integration tests or animation frame verification.
State Matrix Testing
Every stateful widget MUST be tested against ALL possible UI states. Use a state matrix to separate setup from verification:
- Define a
states map with every state the widget can render (loading, error, data, empty).
- Write a single
verify(stateName) callback that asserts correct rendering per state using switch expressions.
- This pattern prevents the common mistake of only testing the "happy path".
- When to use: Widgets with complex state machines (e.g., BLoC-driven screens).
- When NOT to use: If verification logic varies wildly between states, write separate tests.
Interaction Contract Testing
Reusable widgets have implicit behavioral rules. Define these as explicit, reusable contracts:
- Create helper functions in
test/utils/ for common contracts:
verifyTappable(tester, finder, mockCallback): Tap fires callback exactly once.
verifyDisabledNotTappable(tester, finder, mockCallback): Tap does NOT fire callback when disabled.
verifyValidatesOnBlur(tester, finder): Validation triggers when focus leaves.
- Apply contracts consistently across all widgets sharing the same behavior.
- When to use: Widgets with strictly defined behavioral rules that must hold across refactors.
- When NOT to use: One-off logic unique to a single widget.
Test Naming & Structure
- Test Naming: Use string interpolation for test group names:
group('$ClassName', not group('ClassName',. This ensures consistency and enables better tooling support.
- Test Grouping: Use
group() to organize tests by feature, class, or state for clearer reporting.
- Descriptive Names: Test names should clearly describe what is being tested and why.
Common Test Errors
A RenderFlex overflowed...: Wrap widget in Expanded or constrain dimensions in test
Vertical viewport was given unbounded height: Wrap ListView in SizedBox with fixed height in test
setState called during build: Defer state changes to post-frame callback
No MediaQuery widget ancestor: Always wrap test widget in MaterialApp
Running Tests (Quick Reference)
flutter test: Run all unit and widget tests
flutter test test/path/to/file_test.dart: Run specific test file
flutter test integration_test/: Run integration tests
flutter test --coverage: Run with coverage report
dart test: Pure Dart unit tests
1---2name: flutter-testing3description: Use when defining automated testing strategies, organizing test pyramids, or establishing test naming conventions across Flutter apps.4---5
6# Testing Strategy
7
8- **Test Pyramid**: More unit and widget tests, fewer integration tests. Unit tests are fastest and cheapest.
9- **Mirror Test Rule**: 100% logic and widget coverage. No code without a test.
10- **Mirror Organization**: Test files MUST strictly mirror the `lib/` directory structure and end with `_test.dart`.
11- **Coverage Targets**: Target 100% logic coverage for `domain` and `bloc` layers.
12- **Test Independence**: Each test MUST be independent. No shared mutable state between tests.
13
14# Test Types Overview
15
16| Type | Scope | Speed | Skill |
17|---|---|---|---|
18| **Unit** | Single function/class | Fast | `dart-testing` |
19| **Widget** | Single UI component | Medium | `flutter-add-widget-test` |
20| **Integration** | Full app / user flow | Slow | `flutter-add-integration-test` |
21
22# Pattern-Based Testing
23
24These three patterns cut repetitive test setup, cover all visual states, and keep widget behavior consistent. They're conventions, not packages.
25
26## Golden Variant Testing
27When a widget has multiple visual states (primary, disabled, hover, error), test all variants in a single structured `group()` with a `Map<String, Widget Function()>`:
28- Define a `variants` map where keys are state names and values are widget builders.
29- Each variant MUST be rendered in isolation (fresh `pumpWidget` call per variant).
30- Use deterministic golden file naming: `goldens/<component>.<variant>.png`.
31- Set a consistent `surfaceSize` via `tester.view.physicalSize` to avoid flaky pixel diffs.
32- **When to use**: Design system components, widgets with distinct modes/types.
33- **When NOT to use**: Integration tests or animation frame verification.
34
35## State Matrix Testing
36Every stateful widget MUST be tested against ALL possible UI states. Use a state matrix to separate setup from verification:
37- Define a `states` map with every state the widget can render (`loading`, `error`, `data`, `empty`).
38- Write a single `verify(stateName)` callback that asserts correct rendering per state using `switch` expressions.
39- This pattern prevents the common mistake of only testing the "happy path".
40- **When to use**: Widgets with complex state machines (e.g., BLoC-driven screens).
41- **When NOT to use**: If verification logic varies wildly between states, write separate tests.
42
43## Interaction Contract Testing
44Reusable widgets have implicit behavioral rules. Define these as explicit, reusable contracts:
45- Create helper functions in `test/utils/` for common contracts:
46 - `verifyTappable(tester, finder, mockCallback)`: Tap fires callback exactly once.
47 - `verifyDisabledNotTappable(tester, finder, mockCallback)`: Tap does NOT fire callback when disabled.
48 - `verifyValidatesOnBlur(tester, finder)`: Validation triggers when focus leaves.
49- Apply contracts consistently across all widgets sharing the same behavior.
50- **When to use**: Widgets with strictly defined behavioral rules that must hold across refactors.
51- **When NOT to use**: One-off logic unique to a single widget.
52
53# Test Naming & Structure
54
55- **Test Naming**: Use string interpolation for test group names: `group('$ClassName',` not `group('ClassName',`. This ensures consistency and enables better tooling support.
56- **Test Grouping**: Use `group()` to organize tests by feature, class, or state for clearer reporting.
57- **Descriptive Names**: Test names should clearly describe what is being tested and why.
58
59# Common Test Errors
60
61- `A RenderFlex overflowed...`: Wrap widget in `Expanded` or constrain dimensions in test
62- `Vertical viewport was given unbounded height`: Wrap `ListView` in `SizedBox` with fixed height in test
63- `setState called during build`: Defer state changes to post-frame callback
64- `No MediaQuery widget ancestor`: Always wrap test widget in `MaterialApp`
65
66# Running Tests (Quick Reference)
67
68- `flutter test`: Run all unit and widget tests
69- `flutter test test/path/to/file_test.dart`: Run specific test file
70- `flutter test integration_test/`: Run integration tests
71- `flutter test --coverage`: Run with coverage report
72- `dart test`: Pure Dart unit tests