testing-strategy
Skill32 starsChanged 4 months agoMITView on GitHub
Enforces test doctrine where shape follows code not the pyramid: pushes logic into Flutter-free packages tested with pure package:test and an injected Clock; asserts invariants with seeded fuzz tests against an independent oracle plus round-trip and rounding goldens; prefers bare-implements fakes over mocktail for code you own; tests the data layer against a real NativeDatabase.memory Drift engine, never a mocked DAO; drives Notifiers headlessly with ProviderContainer overrides; guards one end-to-end acceptance gate and a runtime invariant tripwire; floors coverage on unrecoverable-bug files not a global percentage; and fixes the coverage-lies-upward gap. Use when writing tests under test/ or integration_test/, choosing unit vs widget vs integration, adding a fuzz or round-trip property, wiring a fake or ProviderContainer test, gating coverage, or triaging a flaky-suite failure.
54 other projects use a version of this, 1 of them edited.
Adopted by
Most recently taken up by
- jDxve/avengers-kittheir own version, first seen Nov 8, 2026
- nisarth/claude-skills-storetheir own version, first seen Nov 8, 2026
- ccoalm/ccl-skillstheir own version, first seen Nov 8, 2026
- komluk/scaffoldingtheir own version, first seen Nov 7, 2026
- bmsuisse/skillstheir own version, first seen Nov 6, 2026
- Cure-Consulting-Group/ProductEngineeringSkillstheir own version, first seen Nov 4, 2026
Used with
Projects with it also use api-contract-design, deploy-checklist and 10 more.
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.
MohOneXSkillapi-contract-design
Designs and reviews API contracts (REST, RPC, GraphQL, library interfaces) for evolvability, backward compatibility, and failure semantics. Use when defining new endpoints/interfaces, versioning an API, reviewing a schema, or deciding how breaking changes propagate to clients.
Also in 3 projects that use this
AnthropicSkilldeploy-checklist
Pre-deployment verification checklist. Use when about to ship a release, deploying a change with database migrations or feature flags, verifying CI status and approvals before going to production, or documenting rollback triggers ahead of time.
Also in 4 projects that use this
xenitV1Skillreact-patterns
Modern React patterns and principles. Hooks, composition, performance, TypeScript best practices.
Also in 4 projects that use this
AnthropicSkilltech-debt
Identify, categorize, and prioritize technical debt. Trigger with "tech debt", "technical debt audit", "what should we refactor", "code health", or when the user asks about code quality, refactoring priorities, or maintenance backlog.
Also in 4 projects that use this
MicrosoftSkillerror-handling
统一错误处理系统。在添加 API 端点、修改错误处理、添加前端 API 调用、编写错误相关测试时使用。
Also in 5 projects that use this
MicrosoftSkillapi-design
Guide for designing and documenting RESTful APIs. Use when asked to design an API, create endpoints, or document an API.
Also in 9 projects that use this
- +4 more from anthropics
- +1 more from microsoft
Other versions54 projects use a version of this.
Word for word · 0
None found.
Edited · 1
- zakariaf/CatchLaw★ 0100% same
Wrote their own version · 53
- anthropics/knowledge-work-plugins★ 35ksame name
- chthollyphile/folia-major★ 12ksame name
- Community-Access/accessibility-agents★ 409same name
- AnastasiyaW/claude-code-config★ 214same name
- AnastasiyaW/codex-claude-code-config★ 214same name
- irahardianto/awesome-agv★ 157same name
- Blushyes/reef★ 84same name
- shinpr/agentic-code★ 51same name
- addxai/enterprise-harness-engineering★ 44same name
- drafthq/draft★ 40same name
43 more not listed
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 repository40 other files it gives its agents.
- AGENTS.mdAGENTS.md
- accessibility-as-codeSkill
- adaptive-layoutSkill
- ads-and-iap-monetizationSkill
- app-startup-and-bootstrapSkill
- async-safetySkill
- ci-pipeline-and-gatesSkill
- codegen-and-toolchainSkill
- custom-canvas-and-gesturesSkill
- dart3-idioms-and-coding-standardsSkill
- dartdoc-conventionsSkill
- data-export-and-restoreSkill
- dependency-hygieneSkill
- design-review-workflowSkill
- design-system-structureSkill
- error-handling-typed-resultsSkill
- flutter-architectureSkill
- flutter-conventions-indexSkill
- flutter-performanceSkill
- forms-and-inputSkill
- i18n-rtl-l10nSkill
- lint-and-style-configSkill
- local-notifications-schedulerSkill
- motion-and-hapticsSkill
- naming-conventionsSkill
- navigation-and-routingSkill
- persistence-driftSkill
- project-structure-and-packagesSkill
- release-and-store-shippingSkill
- run-codegenSkill
- run-goldens-rebaselineSkill
- run-migrationSkill
- scaffold-feature-moduleSkill
- seeded-determinism-and-golden-vectorsSkill
- service-boundary-and-nativeSkill
- state-management-riverpodSkill
- ui-states-and-feedbackSkill
- value-objects-money-and-unitsSkill
- widget-compositionSkill
- widget-golden-and-a11y-testingSkill
The file
We split this skill into the rules it gives an agent
4 rules. 0 are common, 1 is distinctive, and 3 are very specific to this project.
0
Common
Most of the 44 edited versions we read keep these.
1
Distinctive
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 23
Read the reference for the task at hand:
- Very specific: line 28
Run scripts/check_test_hygiene.sh and scripts/run_tests.sh before a PR.
- Very specific: line 50
Assert invariants, not just examples: Every conversion has a round-trip test (decode(encode(x)) == x) and rounding goldens at …
- Very specific: line 57
Test the data layer against a real in-memory engine: Use NativeDatabase.memory(), never a mocked DAO — a mocked DAO proves nothing about …
“Common” is measured: kept by at least 6 in 10 of the 44 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 Strategy
Tests are the only correctness instrument you control at build time: shape the suite
to the code, not to a decades-old ratio, and make each test assert behaviour a build
either passes or fails. Applies to every test under test/ and integration_test/.
Read the reference for the task at hand:
references/test-layers.md— per-layer harness, imports, teardown, edge tables (pure core, in-memory Drift, headless Notifier, integration).references/property-and-fakes.md— fakes-over-mocks, enum-driven fake state, seeded fuzz, independent oracles, absence-of-a-failure-class tests.references/coverage-and-budget.md— file-level floors, the coverage-lies-upward fix, the suite-time budget, and the manual-pass handoff.
Run scripts/check_test_hygiene.sh and scripts/run_tests.sh before a PR.
Golden, RTL, and a11y widget mechanics live in widget-golden-and-a11y-testing; this
skill governs everything below the pixel.
Non-negotiable rules
- Shape the suite to the code, not the pyramid. The 70/20/10 numbers trace to a
2011 test-size heuristic (its author said they were "pulled out of a hat") and
never described Flutter's unit/widget/integration taxonomy. Test at the cheapest
tier that can assert the behaviour: anything expressible as
f(input) -> outputis a unit or property test, never apumpWidget. Driving pure logic through the widget tree is slower, flakier, and hides which layer broke. - Put business rules in a Flutter-free package and inject a
Clock. Domain math lives in pure Dart with zero Flutter/plugin/IO imports, tested withpackage:test(notflutter_test). The one time type ispackage:clock'sClock— NEVERDateTime.now(), never a bespokeClockService. Purecorereads the ambientclock.now(), pinned in tests withwithClock(Clock.fixed(t), …); Riverpod/feature code reads the injectedclockProvider, overridden withclockProvider.overrideWithValue(Clock.fixed(t))(seam owned byvalue-objects-money-and-units). The ban is structural: a pure package declares no Flutter SDK constraint. - Assert invariants, not just examples. Every conversion has a round-trip
test (
decode(encode(x)) == x) and rounding goldens at half-way/boundary values; every universal claim is a seeded fuzz loop (for (var seed = 0; seed < N; seed++)) or property test, checked against an independent oracle — never the production function under test. Print the generated input inreason:so a failure is its own minimal repro.==on integers only;closeTo(x, 1e-9)on doubles. - Test the data layer against a real in-memory engine. Use
NativeDatabase.memory(), never a mocked DAO — a mocked DAO proves nothing about SQL, constraints, indexes, or migrations. Mock repositories only above the data layer.addTearDown(db.close)and close streams synchronously so reactive.watch()streams do not leak "Timer still pending". - Prefer bare-
implementsfakes over mocks for code you own. Aclass FakeX implements X(nonoSuchMethodsuperclass) makes an interface change a compile error, models the risk (state, not call-order) as a field, and doubles as the contract's documentation. Reservemocktailfor genuinely external dependencies; nevermockito/@GenerateMocks(codegen, no null-safety win). registerFallbackValuefor every custom type passed toany()/captureAny()insetUpAll. mocktail throws at runtime, not compile time, on a missing fallback — this is the single most common mocktail failure.- Drive Notifiers headlessly with
ProviderContainer. Override provider dependencies viaoverrideWith; never pump a widget to test state. Assert on the exposedAsyncValue/state, drive actions through.notifier, and dispose the container in teardown. - Guard one end-to-end acceptance gate. A single realistic scenario asserted to
the exact expected result, plus the conservation invariant (parts sum to the
whole). It is the test that proves the pieces compose. Back it with a runtime
assert tripwire inside the primitive itself (
assert(sum(result) == total)) — free in release, catches the bug the moment it happens. - Hold an unrecoverable-bug-files floor by diff-review, not a coverage gate.
There is no automated percentage gate — coverage is a published report
(
ci-pipeline-and-gatesowns that). Instead hold a 100% floor on the handful of files (migrations, the money/allocate primitive, the parser with wire-format traps) where a gap is silent data loss — enforced by reading the diff, not by counting lines. A directory or global percentage gate rewards vanity tests and exclusion churn. First fix the coverage-lies-upward gap:flutter test --coverageomits files no test imports, so the number overstates safety. - Never
pumpAndSettle()on an indefinite animation (splash, shimmer, spinner) — it hangs on a 10-minute timeout. Use timedpump(Duration)withfakeAsync. - Keep the suite fast, and hand structurally-untestable paths to a manual pass. A suite that costs minutes gets skipped, and a skipped suite is a distrusted one. Anything an emulator cannot reproduce (real audio, OEM device diversity, native surfaces without a Flutter engine) is enumerated in a named manual pre-release pass, not faked green — a green test that proves nothing is worse than an admitted gap.
Tier the test to the code (package:test vs flutter_test)
Pure logic runs under dart test with no widget binding; only UI and plugin-wrapper
tests need flutter_test.
// test/domain/money_test.dart — pure Dart, `dart test`, no flutter_test import.
import 'package:test/test.dart';
import 'package:app_core/app_core.dart';
void main() {
// Round-trip: encoding is lossless across the ISO-4217 exponent range.
test('minor-units round-trip for exponents 0, 2, 3', () {
for (final currency in [Currency.jpy, Currency.usd, Currency.kwd]) {
final money = Money(amountMinor: 12345, currency: currency);
expect(Money.parse(money.format(), currency), equals(money));
}
});
}Inject the clock; pin it in tests
Production reads clock.now(); tests freeze it so time-dependent logic is
deterministic — no wall clock, no flake.
// Production: a Reminder is due when its target time has passed.
bool isDue(Reminder r) => !clock.now().isBefore(r.dueAt);
// test/domain/reminder_test.dart
test('reminder is due exactly at its due instant', () {
final due = DateTime.utc(2026, 1, 1, 9);
withClock(Clock.fixed(due), () {
expect(isDue(Reminder(dueAt: due)), isTrue);
});
});For code behind a provider, inject clockProvider and freeze it in a container test
with clockProvider.overrideWithValue(Clock.fixed(due)) — same Clock, same fixed
instant, driven through Riverpod instead of the ambient zone.
Seeded fuzz against an independent oracle
Twenty lines of package:test give you what a stale property package would, with the
generated input as the built-in minimal repro. The oracle must be independent of the
function under test.
// test/domain/allocate_test.dart
test('allocate: parts always sum to the input (fuzz)', () {
final rng = Random(0xC0FFEE);
for (var seed = 0; seed < 500; seed++) {
final n = rng.nextInt(20) + 1;
final total = rng.nextInt(100000);
final weights = List.generate(n, (_) => rng.nextInt(500));
final parts = allocate(amountMinor: total, weights: weights);
expect(parts.fold(0, (a, b) => a + b), equals(total),
reason: 'seed=$seed total=$total weights=$weights'); // its own repro
}
});Real in-memory Drift, never a mocked DAO
// test/data/order_dao_test.dart
void main() {
late AppDatabase db;
setUp(() => db = AppDatabase(NativeDatabase.memory()));
tearDown(() => db.close()); // reactive .watch() streams must not leak timers
test('CHECK constraint rejects a negative quantity', () {
expect(
() => db.orderDao.insertItem(const OrderItem(quantity: -1)),
throwsA(isA<SqliteException>()), // proves the real constraint, not a stub
);
});
}Headless Notifier tests with ProviderContainer
// test/features/cart/cart_notifier_test.dart
void main() {
test('addItem publishes the new total', () async {
final container = ProviderContainer(
overrides: [orderRepositoryProvider.overrideWith((ref) => _FakeOrderRepo())],
);
addTearDown(container.dispose);
await container.read(cartNotifierProvider.future);
await container.read(cartNotifierProvider.notifier).addItem(_anItem);
final state = container.read(cartNotifierProvider);
expect(state, isA<AsyncData<CartState>>());
expect(state.requireValue.totalMinor, 1299);
});
}ProviderContainer.test() (Riverpod 3.x) auto-disposes and is the preferred form when
available; otherwise addTearDown(container.dispose).
Drive timers with fakeAsync
test('debounced search fires once after the window', () {
fakeAsync((async) {
final calls = <String>[];
final debouncer = Debouncer(const Duration(milliseconds: 300));
debouncer.run(() => calls.add('a'));
async.elapse(const Duration(milliseconds: 299));
expect(calls, isEmpty);
async.elapse(const Duration(milliseconds: 1));
expect(calls, ['a']);
});
});Anti-patterns
- Inverting the pyramid — driving pure
f(input) -> outputlogic throughpumpWidget. Slower, flakier, hides the broken layer. DateTime.now()/ ambientRandom()reachable from domain logic. Makes "identical inputs -> identical output" untestable; injectClockand a seed.flutter_testimported into a pure package. Breaks the structural boundary and slows the fastest tier; the package should declare no Flutter SDK constraint.- A mocked DAO standing in for the database. Proves nothing about SQL,
constraints, indexes, or migrations. Use
NativeDatabase.memory(). ==on doubles, or covering a universal claim with one lucky example instead of a fuzz loop over an independent oracle.- A fake that lies about its contract (a dependency that can never fail) — the test then reflects a happy-path stub, not real behaviour. Fakes must expose failure.
- Missing
registerFallbackValuefor a customany()argument — a runtime throw no compiler catches. pumpAndSettle()on an indefinite indicator — hangs on the 10-minute timeout.- A global/directory coverage-percentage gate — rewards vanity tests, and the raw number lies upward. Floor the unrecoverable files; publish the rest.
- A green test over a structurally-untestable path (real audio, native tile, device diversity) — worse than an admitted gap, because it stops anyone checking by hand.
- A test with no meaningful
expect, shared mutable state across tests, or a vague name liketest('works').
Definition of done
- Each test lives at the cheapest tier that can assert it; pure logic under
package:test, widget/integration only where the UI/plugin seam is the point. - Domain logic reads
clock.now(), neverDateTime.now(); tests pin it withwithClock. - Every conversion has a round-trip test and boundary rounding goldens; every
universal claim is a seeded fuzz/property test against an independent oracle,
with the input echoed in
reason:. - The data layer is tested against
NativeDatabase.memory(), not a mocked DAO;db.close()in teardown. - Owned collaborators are bare-
implementsfakes;mocktailis reserved for external deps withregisterFallbackValuefor every customany()type. - Notifiers are tested through
ProviderContaineroverrides, disposed in teardown; no widget pumped to test state. - One acceptance gate asserts a realistic scenario exactly and the conservation
invariant; the primitive carries a runtime
asserttripwire. - Coverage floors sit on the unrecoverable-bug files; the coverage-lies-upward gap is fixed (untested files included); generated files stripped.
- No
pumpAndSettle()on an indefinite animation; timers driven byfakeAsync. - The suite stays within its time budget; every structurally-untestable path is named in the manual pre-release pass, not faked green.
Related skills
- See
widget-golden-and-a11y-testingfor golden lanes, the overflow/RTL matrix, and honest a11y limits. - See
value-objects-money-and-unitsfor theallocate()primitive andClockinjection this skill tests. - See
persistence-driftfor the DAO/value-object seam the in-memory tests exercise. - See
run-migrationfor the forward-only migration ritual whose from→to paths get the 100% floor. - See
error-handling-typed-resultsfor the sealedResult/Failurethe exhaustive-switch tests assert on. - See
async-safetyfor the dropped-Future holes the silence-review checklist targets. - See
service-boundary-and-nativefor the injectable ports the fakes stand in for. - See
ci-pipeline-and-gatesfor how these lanes and greps are wired as gates. - See
seeded-determinism-and-golden-vectorsfor pinning a seeded generator with a frozen fingerprint table when output must reproduce across devices and releases.
References
- Flutter testing overview — https://docs.flutter.dev/testing/overview
- Riverpod testing — https://riverpod.dev/docs/essentials/testing
- Drift testing (NativeDatabase.memory) — https://drift.simonbinder.eu/docs/testing/
package:clock— https://pub.dev/packages/clockpackage:fake_async— https://pub.dev/packages/fake_asyncpackage:mocktail— https://pub.dev/packages/mocktail
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.

