| name | xunit-test-patterns |
| description | 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. |
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."