mock-stub-designer
Designs the minimal set of test doubles for a unit or integration test and decides, per dependency, whether to stub, fake, spy, mock, or exercise the real thing. Use when someone asks "should I mock this", "how do I test code that calls Stripe or S3 or the clock", "why do our tests pass while production is broken", or when a test touches an HTTP API, payment or email/SMS SDK, database, filesystem, system clock, or randomness. Do NOT use for fabricating valid domain fixture data (a User with defaults, an Order with line items) - use test-data-builder instead; for driving a red-green implementation loop, use tdd-expert.
- License 许可
- MIT
- 最近更新
- 2026-10-14
- Stars
- ★ 25
- Forks
- 3
01 前言 Front Matter
- name
- mock-stub-designer
- metadata
- { "title": "Mock Stub Designer" }
- description
- Designs the minimal set of test doubles for a unit or integration test and decides, per dependency, whether to stub, fake, spy, mock, or exercise the real thing. Use when someone asks "should I mock this", "how do I test code that calls Stripe or S3 or the clock", "why do our tests pass while production is broken", or when a test touches an HTTP API, payment or email/SMS SDK, database, filesystem, system clock, or randomness. Do NOT use for fabricating valid domain fixture data (a User with defaults, an Order with line items) - use test-data-builder instead; for driving a red-green implementation loop, use tdd-expert.
03 SKILL.md 正文
Mock Stub Designer
A test double exists to make a test fast, deterministic, and focused - never to mock everything in sight. Over-mocking yields tautological tests that pass against a broken system because they only verify the mocks. This skill decides, per dependency, the weakest double that still isolates the boundary, so the test exercises real behavior everywhere else.
Operating procedure
Step 1: Gather inputs
Collect before designing anything:
- The unit under test and the behavior being verified (one sentence).
- Every dependency the code touches, direct and transitive-at-the-boundary: HTTP/third-party APIs, payment/email/SMS SDKs, database, filesystem, clock, randomness, queues.
- Language and test stack (this picks the wire-interception tool: nock or msw for JS, WireMock for JVM, VCR-style cassettes for Ruby/Python, responses/respx for Python).
- Whether a sandbox environment exists for the critical integration backstop.
If the dependency list is unknown, trace the code first - guessing the boundary list is how internal collaborators end up mocked.
Step 2: Classify each dependency
For each dependency: external boundary you do not own (third-party HTTP, payment/email/SMS SDK, clock, randomness, filesystem, network) versus your own code (domain objects, pure functions, cheap deterministic collaborators). Double only the boundaries you do not own or cannot control. Use the real thing for your own domain objects - mocking them turns the test into a tautology.
Step 3: Pick the weakest double that works
Apply this decision table top to bottom; stop at the first row that fits.
| Double | What it is | Use when | Red flag |
|---|---|---|---|
| Real | The actual collaborator | Your own code: domain objects, pure functions, fast deterministic classes | Mocking these tests nothing |
| Stub | Canned answers to queries, no assertions | The dependency only supplies data the test needs | Asserting on stub calls - that makes it a mock |
| Fake | Working lightweight implementation (in-memory repository, SQLite) | Many tests need a behaving dependency with state | The fake drifting from the real contract - pin it with a shared contract test |
| Spy | Records calls on the real object | Verify a side effect happened without replacing behavior | Asserting internal call sequences |
| Mock | Pre-programmed interaction expectations | The interaction IS the contract: charge exactly once, emit the audit event, send exactly one email | Any interaction assertion where the call is an implementation detail |
Thresholds: if a single test needs more than 3 doubles, the design is too coupled - report that as the finding rather than papering over it. A fake earns its maintenance cost when 3 or more tests reuse it; below that, stub inline. Allow at most one interaction assertion per test, on the one call that is the contract.
Step 4: Intercept HTTP at the wire, not your client
Use nock, msw, WireMock, or VCR-style cassettes so serialization and URL building are still exercised. Record real responses once, replay them, and refresh cassettes on a schedule (a weekly CI job, or immediately when the provider announces a contract change) so they do not drift from the real API.
Step 5: Inject or freeze time and randomness
Never call the wall clock or RNG directly in code that must be deterministic; inject a clock and a random source, or freeze them (fake timers, freezegun). Real-clock tests are flaky near boundaries and untestable for time logic.
Step 6: Keep one real integration test as a backstop
For each critical integration (payments, primary datastore), retain at least one test against a real sandbox dependency so the doubles cannot lie forever. Run it in CI even if it is slower and quarantined from the fast suite.
Worked artifact: good vs bad double design
Scenario: OrderService.place(order) charges a payment gateway over HTTPS and saves via OrderRepository.
Bad - tautological, refactor-hostile:
def test_place_order_bad():
order = Mock() # mocks own domain object
order.total.return_value = 100
repo = Mock()
gateway = Mock() # mocks own client wrapper, skips the wire
svc = OrderService(repo, gateway)
svc.place(order)
gateway.charge.assert_called_once_with(100)
repo.save.assert_called_once() # asserts internal call choreography
assert repo.save.call_args[0][0].status == order.status # verifies the mock, not the system
Every assertion checks the mocks' own wiring. Serialization, URL building, and Order's real total logic are untested; renaming an internal method breaks the test while a broken system still passes it.
Good - real domain, fake state, wire-level stub, one interaction assertion:
def test_place_order_good(responses_mock):
order = build_order(items=[item(price=100)]) # real domain object (test-data-builder)
repo = InMemoryOrderRepository() # fake, reused across the suite
responses_mock.post( # wire-level stub: URL + JSON exercised
"https://api.pay.example/v1/charges",
json={"id": "ch_1", "status": "succeeded"}, status=200,
)
svc = OrderService(repo, HttpPaymentGateway(clock=frozen_clock))
svc.place(order)
assert repo.get(order.id).status == OrderStatus.PLACED # observable outcome
assert len(responses_mock.calls) == 1 # the ONE contract interaction: charged exactly once
Deliverable
Produce a double design for the test: a table of every dependency with its classification (own code vs boundary), the chosen double type with the one-line justification, the wire-interception tool for HTTP, the clock/randomness injection point, and the named real-sandbox backstop test for each critical integration.
Quality bar
- Every double covers a boundary the team does not own; nothing internal and deterministic is mocked.
- HTTP is stubbed at the wire layer, not at a hand-mocked client wrapper.
- Time and randomness are injected or frozen, never read live in tested logic.
- A single fake implementation is reused across tests (and contract-tested against the real thing), not re-stubbed per test.
- Interaction assertions appear at most once per test, only where the interaction is the contract.
- No test carries more than 3 doubles without an accompanying design-smell finding.
Do NOT
- Do NOT mock the unit under test's own domain objects, pure functions, or cheap deterministic collaborators - use them for real.
- Do NOT assert call counts or argument order for harmless internal calls; that breaks on every refactor while catching nothing.
- Do NOT mock three layers deep. If you must, the design is too coupled - report that as the finding instead of papering over it with doubles.
- Do NOT skip the wire layer by mocking your HTTP client; you lose coverage of serialization and URL building.
- Do NOT let cassettes and fakes drift - refresh recordings on a schedule and pin fakes with contract tests.
- Do NOT use this skill for fabricating valid domain or fixture data (a User with defaults, an Order with line items) - use test-data-builder. This skill decides whether and how to fake a dependency, not how to construct domain values.
04 同仓库更多 Skills
zone-2-cardio-plan
Use when someone asks to build a steady-state cardio plan, train an aerobic base or Zone 2, structure easy or endurance weekly cardio, train by heart-rate zones, or improve resting heart rate, VO2, or endurance. Builds a heart-rate-zone aerobic training block with session structure, modality choice, and progression. General fitness guidance, not medical advice. Do NOT use to design a strength or lifting program (use Strength Training Plan), high-intensity interval conditioning, HIIT, or circuits (use Conditioning & HIIT Program), or a mobility/flexibility routine (use Mobility Routine).
youtube-script-writer
Writes retention-engineered long-form YouTube scripts on a hook/body/payoff architecture - a first-30-seconds hook that survives the retention cliff, a beat-sheet body with mini-hooks and bridges, timed pattern interrupts, and a payoff that over-delivers - with B-roll and editing cues marked inline. Use when someone says "script my next video", "my retention graph dies at the start", "tighten this draft", or "outline a video on X". Do NOT use for the 3-second opener of TikTok, Reels, or Shorts - use video-hook-writer instead; do NOT use for spoken voiceover synced to on-screen tutorial actions - use narration-script instead; do NOT use for a sales demo built on a buyer's discovery notes - use demo-script instead; do NOT use to design the thumbnail and title - use thumbnail-concept instead.
win-back-campaign
Designs segmented win-back email sequences for dormant users - dormancy tiers, a three-beat sequence with a single proportional incentive, and a suppression rule - and measures success by re-activation, not opens. Use when someone says "our users went quiet", "design a win-back campaign", "how do I re-engage lapsed customers", "should we email people who haven't logged in for months", or lifecycle messaging has stopped landing with a lapsed segment. Do NOT use for recovering an unfinished checkout - use abandoned-cart-sequence instead. Do NOT use for diagnosing and fixing why active customers cancel - use churn-reduction instead. Do NOT use for building always-on onboarding or nurture flows - use email-drip-builder instead.
wikipedia-style
Writes neutral, verifiable, encyclopedic prose following Wikipedia's core content policies and manual of style - NPOV, inline citations, notability-backed claims, and a strict ban on promotional language. Use when someone asks "write this like a Wikipedia article", "draft a Wikipedia page for this company or person", "make this neutral and encyclopedic", or "why does my article draft keep getting rejected". Do NOT use for persuasive or opinion writing - use op-ed-writer instead; for thesis-driven academic papers, use academic-essay; for verifying claims in existing text, use fact-checker.
whitepapers
Structures and writes authoritative B2B whitepapers - a standalone executive summary, a data-sized problem statement, methodology, evidence-driven findings, recommendations, and a soft call to action - flagging every claim that needs a real citation. Use when a marketer asks "write a whitepaper on this topic", "turn our research into a lead-gen asset", "make this credible enough for executives", or a draft reads like a product pitch instead of analysis. Do NOT use for customer-story-driven proof - use case-study-builder instead; for policy audiences, use policy-brief.
white-space-analysis
Finds unmet market gaps by ranking jobs-to-be-done on importance versus satisfaction, overlaying them against current solutions and workarounds, and validating that each gap is real, winnable, and worth winning. Use when someone asks "where are the gaps in this market", "what's underserved here", "where should we play next", or is hunting for product, feature, or market-entry opportunities. Do NOT use for profiling competitors and building battle cards - use competitive-intelligence instead; for putting a dollar figure on an identified opportunity - use market-sizing instead; for extracting jobs-to-be-done from raw customer interviews - use jtbd-extractor instead.