Best practices for Dart unit tests, Flutter widget tests, and golden file tests.
when_to_use
Use when writing, modifying, or reviewing tests that use package:test, package:flutter_test, package:mocktail, or package:bloc_test.
argument-hint
[file-or-directory]
allowed-tools
Read Glob Grep mcp__very-good-cli__test
Dart & Flutter Testing
Testing fundamentals for Dart and Flutter projects — unit tests, widget tests, and golden file tests — using package:test, package:flutter_test, package:mocktail, and package:bloc_test.
Core Standards
Apply these standards to ALL test work:
Descriptive test names — verbose, readable names that describe the behavior; never 'works' or 'renders'
Hierarchical group/test structure that reads as natural sentences — top-level group for the class, nested group for the method, test for the behavior (e.g., UserRepository → getUser → )
returns User when API succeeds
String interpolation for type references — use 'returns $User' not 'returns User' so renames propagate automatically
Private mocks per file — declare class _MockX extends Mock implements X {} with underscore prefix to prevent cross-file coupling
Contained test setup within groups — all setUp/tearDown calls live inside a group, never at the top level of main()
Initialize mutable objects in setUp() with late — declare late MyDep dep; then assign in setUp so each test gets a fresh instance
No shared mutable state between tests — never use static members, global variables, or top-level final instances that persist across tests
Use package:mocktail — never package:mockito
Constant test tags — use an abstract class TestTag with static const fields; never pass raw string literals as tags
Test behavior, not properties — widget tests focus on functional outcomes; static visual properties validated via golden tests
Use pumpApp test helper — wrap widgets via shared helper in test/helpers/pump_app.dart; never inline pumpWidget(MaterialApp(...))
Tag all golden tests — annotate with TestTag.golden so goldens can run/update independently
Pass directory to the test MCP tool when the project is not at the workspace root — monorepos with the Flutter project in a subdirectory (e.g. mobile/) require directory: 'mobile'; omit it only when pubspec.yaml is at the workspace root
Pass timeout_seconds to the test MCP tool — Flutter tests can hang indefinitely when pumpAndSettle() is called without a timeout; set a cap (e.g. timeout_seconds: 120) so the run is killed instead of stalling
Never test private methods directly. Exercise private logic through the public method that uses it:
// If _normalizeEmail is private, test it through the public createUser method:
test('normalizes email to lowercase before saving', () async {
when(() => repository.save(any())).thenAnswer((_) async {});
await subject.createUser(email: 'Dash@Example.COM');
final captured = verify(() => repository.save(captureAny())).captured;
expect(captured.first.email, equals('dash@example.com'));
});
Widget tests verify that Flutter widgets behave correctly — rendering the right content, responding to user interactions, and navigating as expected. They run in a simulated environment without a real device.
Standards
Rule
Details
Use testWidgets
Every widget test uses testWidgets instead of test
Prefer find.byType
Default finder; use find.text for user-visible content, find.byKey only when type/text is ambiguous
Group by behavior category
Use renders, navigates, calls [MethodName], updates as nested group names
Focus on behavior
Assert what the widget does (shows text, calls callback, navigates); use golden tests for visual appearance
Mock Blocs and Cubits
Use MockBloc/MockCubit from package:bloc_test; never provide real Blocs in widget tests
pumpApp Helper
Create a shared pumpApp helper so every widget test wraps the widget under test consistently:
Initial render — builds the widget tree for the first time
pump()
Trigger a single frame rebuild (after setState, tap, etc.)
pump(Duration)
Advance time by a specific duration (animations, debounce)
pumpAndSettle()
Pump repeatedly until no pending frames — use for animations that must complete
Prefer pump() over pumpAndSettle() — pumpAndSettle can hang when infinite animations (e.g., CircularProgressIndicator) are present. Use pump() for discrete rebuilds.