Xunit test patterns
Diagnose and fix messy, brittle, flaky, slow, or duplicated test suites using Gerard Meszaros's "xUnit Test Patterns" — the canonical catalogue of test smells and the patterns that remove them, and the authoritative Test Double taxonomy (Dummy, Stub, Spy, Mock, Fake). Use this skill whenever the user asks you to clean up, refactor, de-duplicate, or speed up tests; whenever tests are obscure, fragile, erratic/flaky, or full of copy-paste setup; whenever you need the precise difference between a stub, spy, mock, fake, or dummy; or whenever you're designing fixtures and setup/teardown — even if they never mention the book or Meszaros. Also for questions like "why are my tests so flaky/slow", "how do I share setup without coupling tests", "is this a stub or a mock", or "how do I stop duplicating test setup". Enforces: name the smell before fixing it; verify one condition per test; prefer a Fresh Fixture over a Shared Fixture; and use the five-name Test Double taxonomy precisely. This skill owns the repo's reference taxonomy that the other skills map onto.From its SKILL.md
npx -y skills add arcboxlabs/testing-canon --skill xunit-test-patternsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
10.2 KB, as published. Nobody here has run it
xUnit Test Patterns (Meszaros)
This skill makes you clean up test suites the way Gerard Meszaros's xUnit Test Patterns prescribes: name the smell, then apply the pattern that removes it. It is the repo's reference for two things every other skill points back to — the precise Test Double taxonomy and the vocabulary of test smells — so when a user says "my tests are a mess," this is the skill that diagnoses and refactors them.
Meszaros's catalogue is large. This SKILL.md gives you the shared vocabulary, the diagnosis workflow, and the highest-value patterns; the reference files hold the full enumerations. Don't invent smell or pattern names — use the ones here, which are taken from the book.
Examples are language-agnostic pseudocode; adapt to the user's stack and conventions.
The shared vocabulary
Use these terms precisely; the rest of the catalogue depends on them.
- SUT (System Under Test) — the component a test exercises.
- DOC (Depended-On Component) — a collaborator the SUT uses.
- Direct inputs/outputs — what the test passes to, and gets back from, the SUT's own API.
- Indirect inputs — values the SUT receives from a DOC (a return value, a thrown error). A test controls these by substituting the DOC.
- Indirect outputs — calls the SUT makes to a DOC that aren't visible on its own API (an email sent, a row written). A test can only verify these via a substitute.
- Test fixture — everything you set up so the SUT can run: the SUT plus its DOCs and data, in a known state.
- Test Double — any test-specific stand-in for a real DOC (the umbrella term).
The two reasons to use a Test Double: control an indirect input (feed the SUT a value) or verify an indirect output (check the SUT made a call). Which reason applies determines which double you need.
The Test Double taxonomy (the canonical five)
This is the authoritative source the whole repo refers to. Distinguish the five by
what they're for, not by the library that makes them. Full treatment in
references/test-doubles-taxonomy.md.
| Double | Stands in to… | Direction | Test asserts on it? |
|---|---|---|---|
| Dummy Object | fill a required parameter that's never actually used | — | no |
| Test Stub | feed the SUT a controlled indirect input | incoming | no |
| Test Spy | record indirect outputs for the test to check afterward | outgoing | yes (after) |
| Mock Object | check indirect outputs against expectations set up front | outgoing | yes |
| Fake Object | provide a working lightweight implementation (e.g. in-memory DB) | incoming | no |
The load-bearing split: Stub/Dummy/Fake stand in for incoming interactions and are never asserted on; Spy/Mock stand in for outgoing interactions and are the only ones you verify. A Saboteur is a Stub variant that raises errors to test failure paths. Test Spy = capture-then-assert; Mock Object = expectations-checked-as-they-happen.
Overusing Mock Objects and Behavior Verification produces Overspecified Software
(a Fragile Test) — so this is also where the repo's classical-school caution lives;
see "Verification style" below and khorikov-unit-testing/references/test-doubles.md.
The Four-Phase Test
Structure every test in four visible phases, in sequence:
- Setup — build the fixture (the SUT and its DOCs in the needed state).
- Exercise — invoke the SUT (one action).
- Verify — assert the outcome (return value, state, or indirect output).
- Teardown — release anything the test allocated, returning the world to its prior state (often automatic for in-memory fixtures).
Keeping the phases distinct is what makes a test readable and makes smells visible — a bloated Setup signals a General Fixture or Obscure Test; multiple Exercise calls signal more than one behavior under test.
Verification style: state vs behavior
Two ways to verify the outcome:
- State Verification — exercise the SUT, then inspect its (or a Fake's) resulting state and compare to expected. Prefer this.
- Behavior Verification — capture the SUT's indirect outputs (the calls it made) and compare to expected behavior. Necessary for genuine indirect outputs (an email, a published message), but over-using it couples tests to internal collaboration and yields Fragile Tests.
Default to state verification; reserve behavior verification for real indirect outputs
at the boundary. This is the same conclusion khorikov-unit-testing and
art-of-unit-testing reach from their own angles.
Workflow A — Cleaning up a smelly suite
The book's core method: you can't fix what you can't name. Diagnose first.
Step 1 — Identify the smell, by category. Match symptoms to a named smell (full
catalogue in references/test-smells.md):
- Code smells (seen while reading test code): Obscure Test, Conditional Test Logic, Hard-to-Test Code, Test Code Duplication, Test Logic in Production.
- Behavior smells (seen when compiling/running): Assertion Roulette, Erratic Test (flaky/order-dependent), Fragile Test (breaks on unrelated changes), Frequent Debugging, Manual Intervention, Slow Tests.
- Project smells (seen at project scale, usually a symptom of the above): Production Bugs, Buggy Tests, Developers Not Writing Tests, High Test Maintenance Cost.
Step 2 — Trace to the root cause. Smells nest: a Fragile Test often traces to Overspecified Software (too many mocks); an Erratic Test to Interacting Tests on a Shared Fixture; an Obscure Test to a Mystery Guest or General Fixture. Fix the cause, not the symptom.
Step 3 — Apply the matching pattern (full set in references/test-patterns.md).
The highest-value moves:
- Duplicated/again-and-again setup → Creation Method (intent-revealing fixture construction), aggregated into an Object Mother when reused across many tests.
- Many similar tests → Parameterized Test (one method, many data rows).
- Repeated, unclear assertions → Custom Assertion (one named assertion encoding test-specific equality) and Verify One Condition per Test.
- Flaky/slow from a Shared Fixture → move to a Fresh Fixture (see below).
- Hard-to-Test Code → extract a Humble Object (the same pattern Khorikov uses) so the logic is testable without the awkward dependency.
Step 4 — Re-run and confirm the smell is gone without introducing a new one (e.g. don't trade duplication for an over-general Object Mother that becomes a General Fixture).
Workflow B — Writing maintainable tests from the start
- One Verify One Condition per Test: each test pins one behavior, so a failure names itself. (Multiple loosely-related assertions → Assertion Roulette.)
- Build fixtures with Creation Methods behind intent-revealing names, not copy-pasted constructor calls. Keep them to a Minimal Fixture — the smallest setup the test actually needs.
- Reuse construction with Test Utility Methods / Test Helpers; reuse whole
object recipes with an Object Mother. (The popular Test Data Builder is a
closely related, more fluent descendant — not a Meszaros term; see
references/test-patterns.md.) - Organize tests with Testcase Class per Class / per Feature / per Fixture — per Fixture is what lets several tests share an identical Setup cleanly.
- Prefer a Fresh Fixture; only reach for a Shared Fixture under real performance pressure, knowing the risks (next section).
Fixture strategy in brief
The fixture decision drives most behavior smells. Detail in
references/fixture-strategies.md.
- Fresh Fixture — each test builds its own brand-new fixture and tears it down. The default: tests stay independent, repeatable, and order-insensitive.
- Shared Fixture — many tests reuse one fixture instance. Tempting for speed (especially with a real database), but it's the prime cause of Erratic Tests: Interacting Tests, Test Run War (parallel runs colliding), Unrepeatable Test, Lonely Test. Use only when you must, and isolate aggressively (per-developer Database Sandbox, Transaction Rollback Teardown).
- Standard Fixture (reuse the design, not the instance) and Minimal Fixture (smallest possible) reduce General Fixture / Obscure Test problems.
Reference files
references/test-doubles-taxonomy.md— the five doubles in full, indirect inputs/outputs, state vs behavior verification, and the mapping other skills use.references/test-smells.md— the complete smell catalogue by category, with the named sub-causes and how to confirm each.references/test-patterns.md— the fix patterns: creation, data, assertion, organization, and test-double configuration patterns.references/fixture-strategies.md— Fresh vs Shared fixtures, transient vs persistent, setup/teardown patterns, and the Shared-Fixture→Erratic-Test trade-off.
../../EXAMPLES.md (repo root) has worked before/after pairs under "xUnit Test
Patterns."