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.
Widget Testing
- Write widget tests for all major UI components.
- Test user interactions and state changes.
- Widget Keys: Use
Key('feature_action_id') format on interactive widgets for test access.
- Test Localization: Use
AppLocalizations (context.l10n) in widget tests — no hardcoded strings.
Pattern-Based Testing
Adopt these three structural patterns to eliminate boilerplate, enforce complete coverage, and ensure consistency across the test suite. These are conventions — no external package dependency is required.
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.
Integration Testing
- Use
IntegrationTestWidgetsFlutterBinding.ensureInitialized() at the start of integration tests
- Interact with widgets via
Key (e.g., find.byKey(const ValueKey('increment')))
- Use
pumpAndSettle() to wait for animations and async operations to complete
- Run with:
flutter test integration_test/
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
Workflow: Testing Execution
Follow this sequential workflow when testing a feature. Copy the checklist to track progress.
Task Progress
Running Tests
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
Source: dhruvanbhalara/skills — distributed by TomeVault.
1---2name: flutter-testing-33description: Write comprehensive widget and integration tests using pattern-based testing (Golden Variants, State Matrix, Interaction Contracts). Use when testing UI components, user interaction flows, or running end-to-end integration tests. Use when this capability is needed.4---56# Testing Strategy78- **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.1314# Widget Testing1516- Write widget tests for all major UI components.17- Test user interactions and state changes.18- **Widget Keys**: Use `Key('feature_action_id')` format on interactive widgets for test access.19- **Test Localization**: Use `AppLocalizations` (`context.l10n`) in widget tests — no hardcoded strings.2021# Pattern-Based Testing2223Adopt these three structural patterns to eliminate boilerplate, enforce complete coverage, and ensure consistency across the test suite. These are conventions — no external package dependency is required.2425## Golden Variant Testing26When a widget has multiple visual states (primary, disabled, hover, error), test all variants in a single structured `group()` with a `Map<String, Widget Function()>`:27- Define a `variants` map where keys are state names and values are widget builders.28- Each variant MUST be rendered in isolation (fresh `pumpWidget` call per variant).29- Use deterministic golden file naming: `goldens/<component>.<variant>.png`.30- Set a consistent `surfaceSize` via `tester.view.physicalSize` to avoid flaky pixel diffs.31- **When to use**: Design system components, widgets with distinct modes/types.32- **When NOT to use**: Integration tests or animation frame verification.3334## State Matrix Testing35Every stateful widget MUST be tested against ALL possible UI states. Use a state matrix to separate setup from verification:36- Define a `states` map with every state the widget can render (`loading`, `error`, `data`, `empty`).37- Write a single `verify(stateName)` callback that asserts correct rendering per state using `switch` expressions.38- This pattern prevents the common mistake of only testing the "happy path".39- **When to use**: Widgets with complex state machines (e.g., BLoC-driven screens).40- **When NOT to use**: If verification logic varies wildly between states, write separate tests.4142## Interaction Contract Testing43Reusable widgets have implicit behavioral rules. Define these as explicit, reusable contracts:44- Create helper functions in `test/utils/` for common contracts:45 - `verifyTappable(tester, finder, mockCallback)` — Tap fires callback exactly once.46 - `verifyDisabledNotTappable(tester, finder, mockCallback)` — Tap does NOT fire callback when disabled.47 - `verifyValidatesOnBlur(tester, finder)` — Validation triggers when focus leaves.48- Apply contracts consistently across all widgets sharing the same behavior.49- **When to use**: Widgets with strictly defined behavioral rules that must hold across refactors.50- **When NOT to use**: One-off logic unique to a single widget.5152# Integration Testing5354- Use `IntegrationTestWidgetsFlutterBinding.ensureInitialized()` at the start of integration tests55- Interact with widgets via `Key` (e.g., `find.byKey(const ValueKey('increment'))`)56- Use `pumpAndSettle()` to wait for animations and async operations to complete57- Run with: `flutter test integration_test/`5859# Test Naming & Structure6061- **Test Naming**: Use string interpolation for test group names: `group('$ClassName',` not `group('ClassName',`. This ensures consistency and enables better tooling support.62- **Test Grouping**: Use `group()` to organize tests by feature, class, or state for clearer reporting.63- **Descriptive Names**: Test names should clearly describe what is being tested and why.6465# Common Test Errors6667- `A RenderFlex overflowed...` — Wrap widget in `Expanded` or constrain dimensions in test68- `Vertical viewport was given unbounded height` — Wrap `ListView` in `SizedBox` with fixed height in test69- `setState called during build` — Defer state changes to post-frame callback7071## Workflow: Testing Execution7273Follow this sequential workflow when testing a feature. Copy the checklist to track progress.7475### Task Progress76- [ ] **Step 1: Write Widget Tests.** Ensure all interactive UI elements have `Key`s. Apply Golden Variant, State Matrix, or Interaction Contract patterns as appropriate.77- [ ] **Step 2: Write Integration Tests.** Create end-to-end user flows using `IntegrationTestWidgetsFlutterBinding`.78- [ ] **Step 3: Check Coverage.** Run `flutter test --coverage` and verify targets are met.79- [ ] **Step 4: Run Static Analysis.** Execute `dart analyze` to ensure code conforms to linting rules.8081# Running Tests8283- `flutter test` — Run all unit and widget tests84- `flutter test test/path/to/file_test.dart` — Run specific test file85- `flutter test integration_test/` — Run integration tests86- `flutter test --coverage` — Run with coverage report8788---89> Source: [dhruvanbhalara/skills](https://github.com/dhruvanbhalara/skills) — distributed by [TomeVault](https://tomevault.io).90<!-- tomevault:4.0:skill_md:2026-05-22 -->