flutter-testing
Skill10 starsChanged 41 days agoMITView on GitHub
Use when writing or reviewing tests for a Flutter app that uses Riverpod, Result-based repositories and SDKs behind contracts - unit tests for data sources, repositories and notifiers, widget tests with provider overrides, listener tests with recording fakes, golden tests, or deciding between a mock and a fake.
7 other projects use a version of this.
Adopted by
Most recently taken up by
- rolandtolnay/flutter-llm-toolkittheir own version, first seen Nov 8, 2026
- growerp/growerptheir own version, first seen Nov 1, 2026
- thienqo/flutter-skillstheir own version, first seen Nov 1, 2026
- gspencergoog/skillstheir own version, first seen Oct 16, 2026
- gabuldev/flutter-skillstheir own version, first seen Sep 17, 2026
- thiennc-tesoglobal/flutter-skillstheir own version, first seen Aug 30, 2026
Used with
Projects with it also use dart-concurrency and flutter-accessibility.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
thiennc-tesoglobalSkilldart-concurrency
Design and debug Dart asynchronous infrastructure with Future, Stream, isolates, cancellation, and lifecycle-safe ownership. Use for races, stale results, background CPU work, stream leaks, or async error handling; not for routine form-local validation or submission unless its underlying ordering mechanism is the task.
Also in 3 projects that use this
thiennc-tesoglobalSkillflutter-accessibility
Build or audit Flutter accessibility for semantics, screen readers, focus, keyboard access, text scaling, contrast, target size, and motion preferences. Pair with flutter-responsive-layout when large text causes reflow, clipping, or overflow across constraints.
Also in 3 projects that use this
Other versions7 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 7
- growerp/growerp★ 115same name
- MADTeacher/mad-agents-skills★ 106same name
- thiennc-tesoglobal/flutter-skills★ 16same name
- thienqo/flutter-skills★ 16same name
- gabuldev/flutter-skills★ 12same name
- gspencergoog/skills★ 2same name
- rolandtolnay/flutter-llm-toolkit★ 2same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository5 other files it gives its agents.
The file
We split this skill into the rules it gives an agent
6 rules. 0 are common, 1 is distinctive, and 5 are very specific to this project.
0
Common
Most of the 7 edited versions we read keep these.
1
Distinctive
This author's own choices. Worth a look.
5
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 32
If you find yourself mocking an SDK inside a feature test, the SDK is not behind a contract (see flutter-sdk-contracts).
- Distinctive: line 161
When a feature depends on a library behaviour that is not yours to guarantee (for example, a failed refetch keeps the previous value so …
- Very specific: line 175
Arrange with overrides, not with real wiring: If a test needs Dio or Firebase, a contract is missing.
- Very specific: line 181
No
sleep: Use tester.pump(duration), fake_async, or container.pump(). - Very specific: line 182
Dispose: every ProviderContainer (addTearDown).
- Very specific: line 183
Verify layer rules separately with check_boundaries.sh from flutter-feature-scaffold.
“Common” is measured: kept by at least 6 in 10 of the 7 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Testing a contract-based Flutter app
The architecture makes testing cheap: every boundary is a contract you can override. Test behaviour at the boundaries, not implementation details.
Layout
test/
├── helpers/ test_container.dart, pump_app.dart, fakes, helpers.dart (barrel)
├── core/ mirrors lib/core
├── features/<feature>/ mirrors lib/features/<feature>; goldens/ beside the test
└── shared/One test file per source file, named <file>_test.dart. Group by class.
Mock or fake
| Use | When |
| Recording fake (a hand-written class that implements the contract and stores what it received) | Stateful app-owned contracts: AnalyticsService, ErrorTrackingService, NotificationService. Assertions read as "this event was tracked". |
mocktail mock | A repository or data-source contract with canned answers: when(repo.getDashboard).thenAnswer((_) async => Success(dashboard)). |
Fake ApiClient | Testing a remote data source: throw an ApiException, assert the Result. Never mock Dio here. |
Mock Dio | Only in the DioApiClient test itself. |
If you find yourself mocking an SDK inside a feature test, the SDK is not
behind a contract (see flutter-sdk-contracts).
Helpers (copy from assets/)
test_container.dart:createContainer(overrides:)disposes itself and turns retry off. Riverpod 3 retries failing providers with backoff by default, which makes failure assertions slow and flaky.pump_app.dart:tester.pumpApp(widget, overrides:)mounts under the real theme and localizations and returns the container.recording_analytics_service.dart: from theflutter-sdk-contractsassets.
Recipes
1. Data source: failure mapping
test('maps 401 to AuthFailure', () async {
final api = _FakeApiClient(throws: const ApiResponseException(statusCode: 401));
final result = await ProfileRemoteDataSourceImpl(api).fetchProfile();
expect(result.exceptionOrNull(), isA<AuthFailure>());
});Cover: success, 401/403 → AuthFailure, 422 → ValidationFailure, 5xx →
ServerFailure, connection error → NetworkFailure, timeout → NetworkFailure,
anything else → UnknownFailure.
2. Repository: model → entity
Fake the data source, return a model, assert the entity. Also assert a failure is passed through unchanged.
3. Notifier
test('loads items from the repository', () async {
final repo = _MockItemRepository();
when(repo.getPage).thenAnswer((_) async => Success(page));
final container = createContainer(
overrides: [itemRepositoryProvider.overrideWithValue(repo)],
);
final state = await container.read(itemsProvider.future);
expect(state.items, page.items);
});- Override the repository contract;
data/is never touched. - For
autoDisposeproviders, hold a listener (keepAlivehelper) or the provider can be disposed between reads. - Replace a dependency notifier with a test subclass:
learningProvider.overrideWith(_RecordingLearningNotifier.new).
4. Submission notifier: dispose mid-flight
test('survives disposal while the action is in flight', () async {
final completer = Completer<Result<String>>();
final subscription = container.listen(provider, (_, _) {});
final pending = container.read(provider.notifier).submit(completer.future);
subscription.close(); // the screen goes away
await container.pump();
completer.complete(const Success('late'));
await expectLater(pending, completion(isA<Success<String, AppFailure>>()));
});Without the ref.mounted guard this throws after disposal.
5. Widget
testWidgets('shows an error with a retry', (tester) async {
await tester.pumpApp(
const ItemsPage(),
overrides: [itemRepositoryProvider.overrideWithValue(_FailingRepository())],
);
await tester.pumpAndSettle();
expect(find.text('Try again'), findsOneWidget);
});6. Listener widget
Mount only the listener over an empty child with UncontrolledProviderScope,
override the contracts (recording analytics, mock repository), drive the state
and assert:
final analytics = RecordingAnalyticsService();
final container = createContainer(overrides: [
analyticsServiceProvider.overrideWithValue(analytics),
]);
await tester.pumpWidget(UncontrolledProviderScope(
container: container,
child: const ItemsListeners(child: SizedBox()),
));
container.read(itemSubmissionProvider.notifier).add();
await tester.pump();
expect(analytics.events.single, isA<ItemChangedEvent>());End by unmounting (await tester.pumpWidget(const SizedBox())) before the
container is disposed.
7. DioApiClient
Mock Dio and assert exception mapping: timeouts and connection errors →
ApiConnectionException, bad responses → ApiResponseException with status
and code, anything else → ApiUnknownException. Stub the same method the
adapter calls (dio.request<Map<String, dynamic>>(...) in the reference
adapter).
8. Goldens — assets/golden_test_template.dart
- Tag them (
@Tags(['golden'])) so they can be run or skipped separately. - Load one known font; fix the surface size and
devicePixelRatio. - One golden per visual state, and per shipped locale (copy length changes layout).
- Generate and compare on one OS in CI; font rendering differs across platforms.
- Update deliberately:
flutter test --tags golden --update-goldens.
9. Pin framework behaviour you rely on
When a feature depends on a library behaviour that is not yours to guarantee (for example, a failed refetch keeps the previous value so pull-to-refresh can report the error without blanking the list), write a small test that asserts it:
expect(state, isA<AsyncError<String>>());
expect(state.hasValue, isTrue); // failed AND still holds the old valueA package upgrade that drops it then fails a test instead of showing up as vanishing content on a bad connection, far from any code you changed.
Principles
- Arrange with overrides, not with real wiring. If a test needs
Dioor Firebase, a contract is missing. - Assert outcomes: state, events recorded, entities returned. Avoid
verify(...).called(n)unless the call count is the behaviour. - Regression first. A bug fix starts with a failing test that names the bug in a comment: why it failed, not only what was asserted.
- No
sleep. Usetester.pump(duration),fake_async, orcontainer.pump(). - Dispose every
ProviderContainer(addTearDown). - Verify layer rules separately with
check_boundaries.shfromflutter-feature-scaffold; tests are not the place for import hygiene.
Review checklist
- Overrides at the contract boundary; no SDK or Dio mocked in feature tests
- Recording fakes for stateful services, mocks for canned repository answers
- Retry disabled in test containers; containers disposed
- Every failure branch of a data source has a test
- Notifier tests cover load success, load failure, and a mutation failure that keeps state
- Goldens tagged, one per state and locale, generated on one OS
- A regression test accompanies each bug fix
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

