[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M71PNMEPPHYKHAMCGVAV8AJ2":3,"skill-same:01M71PNMEPPHYKHAMCGVAV8AJ2":100},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":44,"linkOnly":45,"markdownContent":46,"markdownBody":47,"frontMatter":48,"refName":53,"commitSha":54,"skillDirectory":55,"forksCount":14,"repositoryHtmlUrl":56,"repositoryHomepageUrl":57,"ownerHtmlUrl":58,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":59,"install":60,"resources":73},"01M71PNMEPPHYKHAMCGVAV8AJ2","service-virtualization",null,"Decision framework for isolating every external dependency in a test suite: when to use in-process mocks, HTTP stubs (MSW, WireMock), record-replay, fault injection (Toxiproxy), or ephemeral real services (Testcontainers), and how to enforce that no real calls escape in CI. Use when: \"mock service,\" \"stub API,\" \"fake service,\" \"WireMock,\" \"MSW,\" \"Toxiproxy,\" \"test isolation,\" \"dependency management,\" \"stub an external API in CI.\" Not for: consumer-driven contract verification (Pact, broker) — use contract-testing; standing up a full Docker Compose env or seed data — use test-environments; broad resilience\u002Fgame-day fault campaigns — use chaos-engineering. Related: contract-testing, test-environments, api-testing, test-data-management, chaos-engineering.","pattanation\u002FNorvik","pattanation","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F235419268?v=4","01M71M5THHYYB15ME8R2E4FCZG","Patta","01M71M5TJM1NDM992M8PDKYR2K",0,"MIT","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fservice-virtualization\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"virtualization","service","service virtualization","dependency","toxiproxy","how","verification","resilience","game","api","msw","when","how to","ci","decision","up","testing","docker compose","wiremock","chaos-engineering","2026-11-10 13:02:02",false,"---\nname: service-virtualization\ndescription: >-\n  Decision framework for isolating every external dependency in a test suite: when to use\n  in-process mocks, HTTP stubs (MSW, WireMock), record-replay, fault injection (Toxiproxy), or\n  ephemeral real services (Testcontainers), and how to enforce that no real calls escape in CI.\n  Use when: \"mock service,\" \"stub API,\" \"fake service,\" \"WireMock,\" \"MSW,\" \"Toxiproxy,\"\n  \"test isolation,\" \"dependency management,\" \"stub an external API in CI.\"\n  Not for: consumer-driven contract verification (Pact, broker) — use contract-testing; standing\n  up a full Docker Compose env or seed data — use test-environments; broad resilience\u002Fgame-day\n  fault campaigns — use chaos-engineering.\n  Related: contract-testing, test-environments, api-testing, test-data-management, chaos-engineering.\nlicense: MIT\nmetadata:\n  author: kindlmann\n  version: \"2.0\"\n  category: infrastructure\n---\n\n\u003Cobjective>\nMock Stripe at the SDK layer and your tests pass green while production 500s the moment Stripe\nchanges a response field — because the stub was never tied to a contract. This skill picks the\nright isolation strategy per dependency (in-process mock, HTTP stub, record-replay, fault\ninjection, or ephemeral real service), stubs at the HTTP layer so tests survive SDK upgrades, and\nmakes the CI run fail when any real call escapes the stubs.\n\u003C\u002Fobjective>\n\n## Quick Route\n\n| Situation | Go to |\n|-----------|-------|\n| Node\u002Fbrowser test hitting an external HTTP API | MSW → `references\u002Fmsw.md` |\n| Polyglot CI, complex matching, or you need a standalone stub server | WireMock → `references\u002Fwiremock.md` |\n| Dependency is a DB \u002F cache \u002F queue | Testcontainers → `references\u002Ftestcontainers.md` |\n| Testing timeouts, latency, connection resets | Toxiproxy → `references\u002Ftoxiproxy.md` |\n| Bootstrapping stubs from a real API, or a multi-step baseline | Record-replay → `references\u002Frecord-replay.md` |\n| Wiring any of these into GitHub Actions | `references\u002Fci.md` |\n| Not sure which to reach for | Decision Tree (below) |\n\n---\n\n## Discovery Questions\n\nCheck `.agents\u002Fqa-project-context.md` first — if it exists, use it, respect existing mocking\nconventions, and skip anything answered there. Then:\n\n- **How many external dependencies does the system call, and which are painful in tests?**\n  Rate-limited, slow, flaky, or paid dependencies are the highest-priority virtualization\n  candidates; list payment, email, auth, and third-party data sources.\n- **What testing levels need isolation?** Unit tests want fast in-process mocks; integration tests\n  want HTTP-level stubs; E2E wants real or containerized services — the level sets the strategy.\n- **Does the provider offer an official test mode or sandbox?** Stripe test mode, Twilio test\n  creds, etc. beat any home-grown stub for fidelity — prefer them where they exist.\n- **Do you have contracts with these dependencies?** If yes, contract tests keep stubs in sync;\n  see `contract-testing`. If no, mocking what you don't own is a known risk (Core Principle 3).\n- **Docker available in CI?** No Docker steers you to MSW (in-process); Docker unlocks WireMock,\n  Testcontainers, and Toxiproxy.\n\n---\n\n## Core Principles\n\n**1. Match the isolation level to the confidence you need.** Unit tests can mock aggressively —\nthey test internal logic. Integration tests should use realistic stubs or real services because\nthey test boundaries. E2E should run the closest thing to production that is still reliable.\n\n**2. Real services beat fakes when they're fast, free, and reliable.** A local PostgreSQL\ncontainer gives more confidence than a fake and costs little — prefer it. Reserve fakes for\ndependencies that are slow, unreliable, or paid.\n\n**3. Never mock what you don't own without a contract — and prefer the provider's test mode.** If\nyou stub Stripe and Stripe changes its response shape, your tests stay green while production\nbreaks. In order of preference: use the provider's official test mode\u002Fsandbox (Stripe test mode,\nTwilio test creds) → an HTTP stub validated by a contract test for drift → a bare stub only when\nnothing better exists. See `contract-testing`.\n\n**4. Stubs must fail realistically.** A stub that always returns 200 never exercises error\nhandling. Every stub needs error variants: 429 rate limits, 500s, timeouts, malformed bodies.\n\n**5. One abstraction layer between test and tool.** Wrap MSW handlers, WireMock stubs, and\nTestcontainers setup behind a consistent interface so switching tools doesn't mean rewriting tests.\n\n---\n\n## Decision Framework\n\n### When to use each isolation strategy\n\n| Strategy | Speed | Fidelity | Complexity | Best for |\n|----------|-------|----------|------------|----------|\n| **In-process mock** | Fastest | Lowest | Trivial | Unit tests, isolating internal modules |\n| **HTTP stub (MSW)** | Fast | Medium | Low | Frontend\u002FNode tests hitting external APIs |\n| **HTTP stub (WireMock)** | Fast | Medium-High | Medium | Language-agnostic, complex matching rules |\n| **Record-replay** | Fast after first run | High initially, decays | Medium | Bootstrapping stubs from real APIs quickly |\n| **Service fake** | Medium | High | High | Stateful dependencies (in-memory DB, fake auth) |\n| **Ephemeral real (Testcontainers)** | Slower | Highest | Medium | Databases, message queues, caches |\n| **Shared real service** | Slow | Production-level | Low (to set up) | Staging validation, final pre-deploy check |\n\n### Decision tree\n\n```\nIs the dependency internal to your codebase?\n├─ Yes → In-process mock (vi.mock \u002F jest.mock \u002F monkeypatch)\n└─ No → Is it a database, cache, or message queue?\n         ├─ Yes → Testcontainers (ephemeral real instance)\n         └─ No → Is it a third-party HTTP API?\n                  ├─ Yes → Does the provider offer a test\u002Fsandbox mode?\n                  │        ├─ Yes → Use sandbox in staging, MSW\u002FWireMock in CI\n                  │        └─ No → MSW or WireMock + contract test for drift detection\n                  └─ No → Is it an internal microservice?\n                           ├─ Yes → Contract test (Pact) + stub for consumer tests\n                           └─ No → Evaluate case by case\n```\n\n---\n\n## Tools\n\nPick **MSW** as the default for in-process Node\u002Fbrowser tests; **WireMock** for cross-language CI\nor standalone stub servers; **Prism** when the OpenAPI spec is the contract; **Mockoon** for\ndev-time exploratory mocking. Heavy code for each lives in `references\u002F` — pointers below.\n\n### MSW (Mock Service Worker)\n\nMSW 2.x intercepts HTTP at the network layer (Service Worker in the browser, request interception\nin Node). The default for JS\u002FTS projects. Stub at the HTTP layer (`POST \u002Fv1\u002Fpayment_intents`),\nnever at the SDK method — that decouples the test from the SDK version.\n\nThe strongest enforcement seam this skill offers: `setupServer(...).listen({ onUnhandledRequest })`.\nSet it to `\"error\"` in CI so any escaped real call fails the run; `\"warn\"` is fine locally while\niterating.\n\nSee `references\u002Fmsw.md` for centralized stateful handlers (payments), the Vitest setup with the\nCI\u002Flocal `onUnhandledRequest` switch, per-test timeout\u002Fretry overrides, and a full stateful auth\nflow (create \u002F verify \u002F refresh \u002F **revoke via `http.delete`**) keyed on a `Bearer` token.\n\n### WireMock\n\nLanguage-agnostic HTTP stub server (current stable **3.13.2** — 4.0 is beta-only as of mid-2026,\nstay on 3.x for CI). Runs standalone or as a Docker container. Best for polyglot environments or\ncomplex matching rules.\n\nUse **priority-based mappings** for error scenarios: a `priority: 1` mapping that matches an\nopt-in header (`X-Test-Scenario: rate-limit`) and returns 429 shadows the default happy-path\nmapping only when the test asks for it. WireMock also exposes an admin API for programmatic stub\ncreation (`POST \u002F__admin\u002Fmappings`), verification (`POST \u002F__admin\u002Frequests\u002Fcount`), and reset\n(`POST \u002F__admin\u002Fmappings\u002Freset`).\n\nSee `references\u002Fwiremock.md` for the Docker setup, a response-templated paginated mapping, the\npriority error mapping JSON, and the drift-detection seam to `contract-testing` (replay a Pact or\nvalidate the stub against the OpenAPI spec via Prism).\n\n### Other HTTP mock servers\n\n| Tool | Strengths | When to use |\n|------|-----------|-------------|\n| **Mockoon** | Desktop UI + CLI; OpenAPI import; rule-based responses; lightweight | Dev-time mocking and quick CLI mocks in CI |\n| **Hoverfly** | Capture-replay (record real traffic, replay deterministically); capture\u002Fsimulate\u002Fmodify\u002Fsynthesize modes | Migrating from a real dependency to a mock — record once, replay forever |\n| **Prism** | OpenAPI-driven mock server (Stoplight); validates requests + generates responses from spec | OpenAPI-first projects with a published spec |\n| **MockServer** | Java-based; rich expectation matching; multi-protocol | JVM teams already on MockServer |\n\n### Testcontainers\n\nSpin up **real** services in Docker for integration tests — containers start before the suite and\nare destroyed after. Use `@testcontainers\u002Fpostgresql` 11.x and friends. Ports are **random**;\nalways read them via `getMappedPort()` \u002F `getConnectionUri()`, never hardcode 5432\u002F6379.\n\nSee `references\u002Ftestcontainers.md` for parallel PostgreSQL + Redis + Elasticsearch startup with\nwait strategies, the Vitest `globalSetup` wiring (`process.env.DATABASE_URL`, `testTimeout: 30_000`),\nand the note on refreshing aging image tags (`elasticsearch:8.12.0` is behind Elastic 9.x).\n\n### Toxiproxy (fault injection)\n\n`ghcr.io\u002Fshopify\u002Ftoxiproxy:2.12.0` sits between your app and a dependency as a TCP proxy and\ninjects latency, bandwidth limits, and connection resets. Point your app at the **proxy** port,\nnot the real service. Always remove toxics in `afterEach` — they leak across tests otherwise.\n\nSee `references\u002Ftoxiproxy.md` for the compose port mapping, the `createProxy(name, listen,\nupstream)` wiring (including how the compose `15432`\u002F`16379` proxy ports map to the real upstream),\nthe helpers with `response.ok` checks on every call, and a latency + connection-reset usage example.\nFor broad resilience\u002Fgame-day work, go to `chaos-engineering` instead.\n\n---\n\n## Record-Replay\n\nRecord-replay captures real API responses once and replays them deterministically — good for\nbootstrapping stubs and for a regression baseline of a multi-step interaction. Implement it with a\nrecord-replay library (**Hoverfly**, **Polly.JS**, or **VCR**-style cassettes), not a hand-rolled\nrecorder.\n\nIt breaks on dynamic data (timestamps, UUIDs), stateful sequences, and age — recordings go stale\nwithin weeks. Always stamp a `recordedAt` and **fail the test when a recording is older than 30\ndays**, forcing a re-record.\n\nSee `references\u002Frecord-replay.md` for the cassette format, the `assertFresh()` 30-day expiry check,\nand a replay harness driving a multi-step flow (create order → add items → apply coupon → checkout).\n\n---\n\n## CI Integration\n\nMSW needs zero infrastructure — it intercepts in-process, so CI runs exactly like local; the only\nrule is to set `onUnhandledRequest: error` in CI (quoted `\"error\"` in JS) so an escaped real call\nhard-fails the run. WireMock and Testcontainers need Docker.\n\nTwo **incompatible port models** coexist and must not be mixed in one suite: the docker-compose\nmodel publishes **fixed** ports (a hardcoded `DATABASE_URL=...localhost:5432...` works), while the\nTestcontainers model uses **random** ports read via `getMappedPort()` and injected into\n`process.env`. Pick one per suite.\n\nSee `references\u002Fci.md` for the MSW step, the docker-compose GitHub Actions job (`up -d --wait\n--wait-timeout 120`, `if: always()` teardown), and the tool-by-constraint table.\n\n---\n\n## Anti-Patterns\n\n### 1. Mocking everything\nIf every dependency is mocked, your tests verify that your mocks work, not that your system works.\nUse real services for databases and caches (via Testcontainers); only stub external HTTP APIs.\n\n### 2. Inconsistent mock behavior across tests\nOne test stubs Stripe as `{ id: \"pi_123\" }`, another as `{ paymentIntentId: \"pi_123\" }` — now you\nhave two conflicting versions of reality. Centralize handlers and reuse one response shape across\nthe whole suite (see the shared shape in `references\u002Fmsw.md`).\n\n### 3. Not updating stubs when the API changes\nYour mapping says Stripe returns `{ amount: 1000 }` but the real API now returns\n`{ amount: 1000, currency: \"usd\" }`. Tests pass, production fails. Use contract tests to detect\ndrift — see `contract-testing` and the drift seam in `references\u002Fwiremock.md`.\n\n### 4. Stubbing the wrong layer\nMocking `stripe.paymentIntents.create` (the SDK method) couples the test to the SDK version. Stub\nat the HTTP layer (`POST \u002Fv1\u002Fpayment_intents`) so the test works regardless of HTTP client or SDK\nversion.\n\n### 5. No error-scenario coverage\nStubs that always return 200 never exercise retry logic, timeout handling, rate-limit backoff, or\nerror parsing. Every stub needs a corresponding error variant.\n\n### 6. Shared, long-lived mock servers\nA shared WireMock instance that multiple CI jobs hit introduces coupling and state leakage. Each\ntest run starts its own isolated stub server.\n\n### 7. Record-replay without expiration\nRecordings from six months ago reflect an API that no longer exists. Stamp `recordedAt` and fail\nthe test when recordings exceed 30 days, forcing a re-record (see `references\u002Frecord-replay.md`).\n\n---\n\n## Verification\n\nProve no real call escaped, smallest check first:\n\n```bash\n# 1. MSW: any unhandled request must hard-fail the suite in CI\nCI=1 npm run test:integration            # onUnhandledRequest:\"error\" → exit 0 means nothing escaped\n\n# 2. WireMock\u002FTestcontainers: confirm containers are reachable, then teardown leaves nothing\ndocker compose -f docker-compose.test.yml up -d --wait --wait-timeout 120 && echo OK\ndocker compose -f docker-compose.test.yml down -v\n\n# 3. Grep CI logs for outbound calls to the real provider's host (should print nothing)\ngrep -iE \"api\\.stripe\\.com|api\\.twilio\\.com\" ci-run.log && echo \"LEAK\" || echo \"clean\"\n```\n\nA green run under `CI=1` with `onUnhandledRequest:\"error\"` plus an empty grep for the real host is\nthe proof that the suite ran fully virtualized.\n\n---\n\n## Done When\n\n- A dependency isolation strategy is decided and documented for each external dependency (which get\n  MSW\u002FWireMock stubs, which use Testcontainers, which use the provider's sandbox mode).\n- Stubs cover all critical external dependencies with at least one error path each (4xx\u002F5xx,\n  timeout, or rate limit).\n- Stubs and mapping files are versioned alongside test code in the same repository.\n- The suite runs green in CI with `onUnhandledRequest: \"error\"` (or the WireMock equivalent), and a\n  grep of CI logs for the real provider host returns nothing.\n- Any record-replay baseline carries a `recordedAt` stamp and a 30-day expiry check that fails the\n  test when stale.\n\n## Reference Files (in `references\u002F`)\n\n- **msw.md** — centralized stateful handlers, Vitest setup with the CI\u002Flocal `onUnhandledRequest`\n  switch, per-test timeout\u002Fretry overrides, and the full create\u002Fverify\u002Frefresh\u002Frevoke auth flow.\n- **wiremock.md** — Docker setup, response-templated and paginated mappings, the priority-based\n  error mapping, the admin API, and the contract-drift seam (Pact\u002FPrism).\n- **testcontainers.md** — parallel PostgreSQL\u002FRedis\u002FElasticsearch startup, `globalSetup` wiring,\n  and the image-tag refresh note.\n- **toxiproxy.md** — compose ports, `createProxy` upstream wiring, helpers with `response.ok`\n  checks, and a latency + reset usage example.\n- **record-replay.md** — tooling choices, the cassette format, the 30-day `assertFresh` check, and\n  a multi-step replay harness.\n- **ci.md** — MSW (zero-infra), the docker-compose GitHub Actions job, the two port models, and the\n  tool-by-constraint table.\n\n## Related Skills\n\n- **contract-testing** — Consumer-driven contract verification with Pact\u002Fbroker; go there to prove\n  a stub matches a real provider, not just to detect drift against a shared schema.\n- **test-environments** — Full Docker Compose env strategy, preview environments, and seed data; go\n  there for standing up the environment, not for isolating a single dependency.\n- **chaos-engineering** — Broad fault-injection campaigns, game days, and blast-radius limits; go\n  there when resilience itself is the goal rather than making one dependency misbehave in a test.\n- **api-testing** — REST\u002FGraphQL testing patterns, schema validation, and auth flow testing.\n- **test-data-management** — Factory patterns and data seeding for stub state setup.\n","\u003Cobjective>\nMock Stripe at the SDK layer and your tests pass green while production 500s the moment Stripe\nchanges a response field — because the stub was never tied to a contract. This skill picks the\nright isolation strategy per dependency (in-process mock, HTTP stub, record-replay, fault\ninjection, or ephemeral real service), stubs at the HTTP layer so tests survive SDK upgrades, and\nmakes the CI run fail when any real call escapes the stubs.\n\u003C\u002Fobjective>\n\n## Quick Route\n\n| Situation | Go to |\n|-----------|-------|\n| Node\u002Fbrowser test hitting an external HTTP API | MSW → `references\u002Fmsw.md` |\n| Polyglot CI, complex matching, or you need a standalone stub server | WireMock → `references\u002Fwiremock.md` |\n| Dependency is a DB \u002F cache \u002F queue | Testcontainers → `references\u002Ftestcontainers.md` |\n| Testing timeouts, latency, connection resets | Toxiproxy → `references\u002Ftoxiproxy.md` |\n| Bootstrapping stubs from a real API, or a multi-step baseline | Record-replay → `references\u002Frecord-replay.md` |\n| Wiring any of these into GitHub Actions | `references\u002Fci.md` |\n| Not sure which to reach for | Decision Tree (below) |\n\n---\n\n## Discovery Questions\n\nCheck `.agents\u002Fqa-project-context.md` first — if it exists, use it, respect existing mocking\nconventions, and skip anything answered there. Then:\n\n- **How many external dependencies does the system call, and which are painful in tests?**\n  Rate-limited, slow, flaky, or paid dependencies are the highest-priority virtualization\n  candidates; list payment, email, auth, and third-party data sources.\n- **What testing levels need isolation?** Unit tests want fast in-process mocks; integration tests\n  want HTTP-level stubs; E2E wants real or containerized services — the level sets the strategy.\n- **Does the provider offer an official test mode or sandbox?** Stripe test mode, Twilio test\n  creds, etc. beat any home-grown stub for fidelity — prefer them where they exist.\n- **Do you have contracts with these dependencies?** If yes, contract tests keep stubs in sync;\n  see `contract-testing`. If no, mocking what you don't own is a known risk (Core Principle 3).\n- **Docker available in CI?** No Docker steers you to MSW (in-process); Docker unlocks WireMock,\n  Testcontainers, and Toxiproxy.\n\n---\n\n## Core Principles\n\n**1. Match the isolation level to the confidence you need.** Unit tests can mock aggressively —\nthey test internal logic. Integration tests should use realistic stubs or real services because\nthey test boundaries. E2E should run the closest thing to production that is still reliable.\n\n**2. Real services beat fakes when they're fast, free, and reliable.** A local PostgreSQL\ncontainer gives more confidence than a fake and costs little — prefer it. Reserve fakes for\ndependencies that are slow, unreliable, or paid.\n\n**3. Never mock what you don't own without a contract — and prefer the provider's test mode.** If\nyou stub Stripe and Stripe changes its response shape, your tests stay green while production\nbreaks. In order of preference: use the provider's official test mode\u002Fsandbox (Stripe test mode,\nTwilio test creds) → an HTTP stub validated by a contract test for drift → a bare stub only when\nnothing better exists. See `contract-testing`.\n\n**4. Stubs must fail realistically.** A stub that always returns 200 never exercises error\nhandling. Every stub needs error variants: 429 rate limits, 500s, timeouts, malformed bodies.\n\n**5. One abstraction layer between test and tool.** Wrap MSW handlers, WireMock stubs, and\nTestcontainers setup behind a consistent interface so switching tools doesn't mean rewriting tests.\n\n---\n\n## Decision Framework\n\n### When to use each isolation strategy\n\n| Strategy | Speed | Fidelity | Complexity | Best for |\n|----------|-------|----------|------------|----------|\n| **In-process mock** | Fastest | Lowest | Trivial | Unit tests, isolating internal modules |\n| **HTTP stub (MSW)** | Fast | Medium | Low | Frontend\u002FNode tests hitting external APIs |\n| **HTTP stub (WireMock)** | Fast | Medium-High | Medium | Language-agnostic, complex matching rules |\n| **Record-replay** | Fast after first run | High initially, decays | Medium | Bootstrapping stubs from real APIs quickly |\n| **Service fake** | Medium | High | High | Stateful dependencies (in-memory DB, fake auth) |\n| **Ephemeral real (Testcontainers)** | Slower | Highest | Medium | Databases, message queues, caches |\n| **Shared real service** | Slow | Production-level | Low (to set up) | Staging validation, final pre-deploy check |\n\n### Decision tree\n\n```\nIs the dependency internal to your codebase?\n├─ Yes → In-process mock (vi.mock \u002F jest.mock \u002F monkeypatch)\n└─ No → Is it a database, cache, or message queue?\n         ├─ Yes → Testcontainers (ephemeral real instance)\n         └─ No → Is it a third-party HTTP API?\n                  ├─ Yes → Does the provider offer a test\u002Fsandbox mode?\n                  │        ├─ Yes → Use sandbox in staging, MSW\u002FWireMock in CI\n                  │        └─ No → MSW or WireMock + contract test for drift detection\n                  └─ No → Is it an internal microservice?\n                           ├─ Yes → Contract test (Pact) + stub for consumer tests\n                           └─ No → Evaluate case by case\n```\n\n---\n\n## Tools\n\nPick **MSW** as the default for in-process Node\u002Fbrowser tests; **WireMock** for cross-language CI\nor standalone stub servers; **Prism** when the OpenAPI spec is the contract; **Mockoon** for\ndev-time exploratory mocking. Heavy code for each lives in `references\u002F` — pointers below.\n\n### MSW (Mock Service Worker)\n\nMSW 2.x intercepts HTTP at the network layer (Service Worker in the browser, request interception\nin Node). The default for JS\u002FTS projects. Stub at the HTTP layer (`POST \u002Fv1\u002Fpayment_intents`),\nnever at the SDK method — that decouples the test from the SDK version.\n\nThe strongest enforcement seam this skill offers: `setupServer(...).listen({ onUnhandledRequest })`.\nSet it to `\"error\"` in CI so any escaped real call fails the run; `\"warn\"` is fine locally while\niterating.\n\nSee `references\u002Fmsw.md` for centralized stateful handlers (payments), the Vitest setup with the\nCI\u002Flocal `onUnhandledRequest` switch, per-test timeout\u002Fretry overrides, and a full stateful auth\nflow (create \u002F verify \u002F refresh \u002F **revoke via `http.delete`**) keyed on a `Bearer` token.\n\n### WireMock\n\nLanguage-agnostic HTTP stub server (current stable **3.13.2** — 4.0 is beta-only as of mid-2026,\nstay on 3.x for CI). Runs standalone or as a Docker container. Best for polyglot environments or\ncomplex matching rules.\n\nUse **priority-based mappings** for error scenarios: a `priority: 1` mapping that matches an\nopt-in header (`X-Test-Scenario: rate-limit`) and returns 429 shadows the default happy-path\nmapping only when the test asks for it. WireMock also exposes an admin API for programmatic stub\ncreation (`POST \u002F__admin\u002Fmappings`), verification (`POST \u002F__admin\u002Frequests\u002Fcount`), and reset\n(`POST \u002F__admin\u002Fmappings\u002Freset`).\n\nSee `references\u002Fwiremock.md` for the Docker setup, a response-templated paginated mapping, the\npriority error mapping JSON, and the drift-detection seam to `contract-testing` (replay a Pact or\nvalidate the stub against the OpenAPI spec via Prism).\n\n### Other HTTP mock servers\n\n| Tool | Strengths | When to use |\n|------|-----------|-------------|\n| **Mockoon** | Desktop UI + CLI; OpenAPI import; rule-based responses; lightweight | Dev-time mocking and quick CLI mocks in CI |\n| **Hoverfly** | Capture-replay (record real traffic, replay deterministically); capture\u002Fsimulate\u002Fmodify\u002Fsynthesize modes | Migrating from a real dependency to a mock — record once, replay forever |\n| **Prism** | OpenAPI-driven mock server (Stoplight); validates requests + generates responses from spec | OpenAPI-first projects with a published spec |\n| **MockServer** | Java-based; rich expectation matching; multi-protocol | JVM teams already on MockServer |\n\n### Testcontainers\n\nSpin up **real** services in Docker for integration tests — containers start before the suite and\nare destroyed after. Use `@testcontainers\u002Fpostgresql` 11.x and friends. Ports are **random**;\nalways read them via `getMappedPort()` \u002F `getConnectionUri()`, never hardcode 5432\u002F6379.\n\nSee `references\u002Ftestcontainers.md` for parallel PostgreSQL + Redis + Elasticsearch startup with\nwait strategies, the Vitest `globalSetup` wiring (`process.env.DATABASE_URL`, `testTimeout: 30_000`),\nand the note on refreshing aging image tags (`elasticsearch:8.12.0` is behind Elastic 9.x).\n\n### Toxiproxy (fault injection)\n\n`ghcr.io\u002Fshopify\u002Ftoxiproxy:2.12.0` sits between your app and a dependency as a TCP proxy and\ninjects latency, bandwidth limits, and connection resets. Point your app at the **proxy** port,\nnot the real service. Always remove toxics in `afterEach` — they leak across tests otherwise.\n\nSee `references\u002Ftoxiproxy.md` for the compose port mapping, the `createProxy(name, listen,\nupstream)` wiring (including how the compose `15432`\u002F`16379` proxy ports map to the real upstream),\nthe helpers with `response.ok` checks on every call, and a latency + connection-reset usage example.\nFor broad resilience\u002Fgame-day work, go to `chaos-engineering` instead.\n\n---\n\n## Record-Replay\n\nRecord-replay captures real API responses once and replays them deterministically — good for\nbootstrapping stubs and for a regression baseline of a multi-step interaction. Implement it with a\nrecord-replay library (**Hoverfly**, **Polly.JS**, or **VCR**-style cassettes), not a hand-rolled\nrecorder.\n\nIt breaks on dynamic data (timestamps, UUIDs), stateful sequences, and age — recordings go stale\nwithin weeks. Always stamp a `recordedAt` and **fail the test when a recording is older than 30\ndays**, forcing a re-record.\n\nSee `references\u002Frecord-replay.md` for the cassette format, the `assertFresh()` 30-day expiry check,\nand a replay harness driving a multi-step flow (create order → add items → apply coupon → checkout).\n\n---\n\n## CI Integration\n\nMSW needs zero infrastructure — it intercepts in-process, so CI runs exactly like local; the only\nrule is to set `onUnhandledRequest: error` in CI (quoted `\"error\"` in JS) so an escaped real call\nhard-fails the run. WireMock and Testcontainers need Docker.\n\nTwo **incompatible port models** coexist and must not be mixed in one suite: the docker-compose\nmodel publishes **fixed** ports (a hardcoded `DATABASE_URL=...localhost:5432...` works), while the\nTestcontainers model uses **random** ports read via `getMappedPort()` and injected into\n`process.env`. Pick one per suite.\n\nSee `references\u002Fci.md` for the MSW step, the docker-compose GitHub Actions job (`up -d --wait\n--wait-timeout 120`, `if: always()` teardown), and the tool-by-constraint table.\n\n---\n\n## Anti-Patterns\n\n### 1. Mocking everything\nIf every dependency is mocked, your tests verify that your mocks work, not that your system works.\nUse real services for databases and caches (via Testcontainers); only stub external HTTP APIs.\n\n### 2. Inconsistent mock behavior across tests\nOne test stubs Stripe as `{ id: \"pi_123\" }`, another as `{ paymentIntentId: \"pi_123\" }` — now you\nhave two conflicting versions of reality. Centralize handlers and reuse one response shape across\nthe whole suite (see the shared shape in `references\u002Fmsw.md`).\n\n### 3. Not updating stubs when the API changes\nYour mapping says Stripe returns `{ amount: 1000 }` but the real API now returns\n`{ amount: 1000, currency: \"usd\" }`. Tests pass, production fails. Use contract tests to detect\ndrift — see `contract-testing` and the drift seam in `references\u002Fwiremock.md`.\n\n### 4. Stubbing the wrong layer\nMocking `stripe.paymentIntents.create` (the SDK method) couples the test to the SDK version. Stub\nat the HTTP layer (`POST \u002Fv1\u002Fpayment_intents`) so the test works regardless of HTTP client or SDK\nversion.\n\n### 5. No error-scenario coverage\nStubs that always return 200 never exercise retry logic, timeout handling, rate-limit backoff, or\nerror parsing. Every stub needs a corresponding error variant.\n\n### 6. Shared, long-lived mock servers\nA shared WireMock instance that multiple CI jobs hit introduces coupling and state leakage. Each\ntest run starts its own isolated stub server.\n\n### 7. Record-replay without expiration\nRecordings from six months ago reflect an API that no longer exists. Stamp `recordedAt` and fail\nthe test when recordings exceed 30 days, forcing a re-record (see `references\u002Frecord-replay.md`).\n\n---\n\n## Verification\n\nProve no real call escaped, smallest check first:\n\n```bash\n# 1. MSW: any unhandled request must hard-fail the suite in CI\nCI=1 npm run test:integration            # onUnhandledRequest:\"error\" → exit 0 means nothing escaped\n\n# 2. WireMock\u002FTestcontainers: confirm containers are reachable, then teardown leaves nothing\ndocker compose -f docker-compose.test.yml up -d --wait --wait-timeout 120 && echo OK\ndocker compose -f docker-compose.test.yml down -v\n\n# 3. Grep CI logs for outbound calls to the real provider's host (should print nothing)\ngrep -iE \"api\\.stripe\\.com|api\\.twilio\\.com\" ci-run.log && echo \"LEAK\" || echo \"clean\"\n```\n\nA green run under `CI=1` with `onUnhandledRequest:\"error\"` plus an empty grep for the real host is\nthe proof that the suite ran fully virtualized.\n\n---\n\n## Done When\n\n- A dependency isolation strategy is decided and documented for each external dependency (which get\n  MSW\u002FWireMock stubs, which use Testcontainers, which use the provider's sandbox mode).\n- Stubs cover all critical external dependencies with at least one error path each (4xx\u002F5xx,\n  timeout, or rate limit).\n- Stubs and mapping files are versioned alongside test code in the same repository.\n- The suite runs green in CI with `onUnhandledRequest: \"error\"` (or the WireMock equivalent), and a\n  grep of CI logs for the real provider host returns nothing.\n- Any record-replay baseline carries a `recordedAt` stamp and a 30-day expiry check that fails the\n  test when stale.\n\n## Reference Files (in `references\u002F`)\n\n- **msw.md** — centralized stateful handlers, Vitest setup with the CI\u002Flocal `onUnhandledRequest`\n  switch, per-test timeout\u002Fretry overrides, and the full create\u002Fverify\u002Frefresh\u002Frevoke auth flow.\n- **wiremock.md** — Docker setup, response-templated and paginated mappings, the priority-based\n  error mapping, the admin API, and the contract-drift seam (Pact\u002FPrism).\n- **testcontainers.md** — parallel PostgreSQL\u002FRedis\u002FElasticsearch startup, `globalSetup` wiring,\n  and the image-tag refresh note.\n- **toxiproxy.md** — compose ports, `createProxy` upstream wiring, helpers with `response.ok`\n  checks, and a latency + reset usage example.\n- **record-replay.md** — tooling choices, the cassette format, the 30-day `assertFresh` check, and\n  a multi-step replay harness.\n- **ci.md** — MSW (zero-infra), the docker-compose GitHub Actions job, the two port models, and the\n  tool-by-constraint table.\n\n## Related Skills\n\n- **contract-testing** — Consumer-driven contract verification with Pact\u002Fbroker; go there to prove\n  a stub matches a real provider, not just to detect drift against a shared schema.\n- **test-environments** — Full Docker Compose env strategy, preview environments, and seed data; go\n  there for standing up the environment, not for isolating a single dependency.\n- **chaos-engineering** — Broad fault-injection campaigns, game days, and blast-radius limits; go\n  there when resilience itself is the goal rather than making one dependency misbehave in a test.\n- **api-testing** — REST\u002FGraphQL testing patterns, schema validation, and auth flow testing.\n- **test-data-management** — Factory patterns and data seeding for stub state setup.\n",{"name":5,"license":15,"metadata":49,"description":7},{"author":50,"version":51,"category":52},"kindlmann","2.0","infrastructure","claude\u002Finstall-gsd-build-foQdV","d86cd127f527124b5fd0fd0bc679a4d79b7d01d7",".agents\u002Fskills\u002Fservice-virtualization","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik","https:\u002F\u002Fnorvik-orpin.vercel.app","https:\u002F\u002Fgithub.com\u002Fpattanation","2026-07-17 11:17:04",{"source":8,"skillName":5,"commands":61,"prompt":71,"zipAvailable":72},[62,65,68],{"tool":63,"command":64},"npx","npx skills add pattanation\u002FNorvik --skill service-virtualization",{"tool":66,"command":67},"bunx","bunx skills add pattanation\u002FNorvik --skill service-virtualization",{"tool":69,"command":70},"pnpm","pnpm dlx skills add pattanation\u002FNorvik --skill service-virtualization","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fservice-virtualization\u002FSKILL.md",true,[74,80,84,88,92,96],{"path":75,"resourceType":76,"resourceTypeDesc":77,"sizeBytes":78,"sourceHtmlUrl":79},"references\u002Fci.md","REFERENCE","参考文档",2223,"https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fservice-virtualization\u002Freferences\u002Fci.md",{"path":81,"resourceType":76,"resourceTypeDesc":77,"sizeBytes":82,"sourceHtmlUrl":83},"references\u002Fmsw.md",7415,"https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fservice-virtualization\u002Freferences\u002Fmsw.md",{"path":85,"resourceType":76,"resourceTypeDesc":77,"sizeBytes":86,"sourceHtmlUrl":87},"references\u002Frecord-replay.md",2945,"https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fservice-virtualization\u002Freferences\u002Frecord-replay.md",{"path":89,"resourceType":76,"resourceTypeDesc":77,"sizeBytes":90,"sourceHtmlUrl":91},"references\u002Ftestcontainers.md",3214,"https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fservice-virtualization\u002Freferences\u002Ftestcontainers.md",{"path":93,"resourceType":76,"resourceTypeDesc":77,"sizeBytes":94,"sourceHtmlUrl":95},"references\u002Ftoxiproxy.md",3935,"https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fservice-virtualization\u002Freferences\u002Ftoxiproxy.md",{"path":97,"resourceType":76,"resourceTypeDesc":77,"sizeBytes":98,"sourceHtmlUrl":99},"references\u002Fwiremock.md",3663,"https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fservice-virtualization\u002Freferences\u002Fwiremock.md",[101,130,156,179,201,216],{"skillNo":102,"name":103,"nameZh":6,"description":104,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":105,"licenseFileUrl":106,"sourceHtmlUrl":107,"categories":108,"keywords":113,"publishedAt":44},"01M71QDV4DM8T19HEHWATEF1K4","webapp-testing","Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.","Apache-2.0","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fwebapp-testing\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fwebapp-testing\u002FSKILL.md",[109,110],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":111,"categoryName":112,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发",[114,40,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129],"webapp","webapp testing","for","applications","local","frontend","debugging","toolkit","logs","browser","with","playwright","web","behavior","ui","screenshots",{"skillNo":131,"name":132,"nameZh":6,"description":133,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":105,"licenseFileUrl":134,"sourceHtmlUrl":135,"categories":136,"keywords":138,"publishedAt":44},"01M71QDH2GGMXGA9105C9W1N7B","web-artifacts-builder","Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn\u002Fui). Use for complex artifacts requiring state management, routing, or shadcn\u002Fui components - not for simple single-file HTML\u002FJSX artifacts.","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fweb-artifacts-builder\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fweb-artifacts-builder\u002FSKILL.md",[137],{"categoryNo":111,"categoryName":112,"taxonomyType":21,"taxonomyTypeDesc":22},[139,140,141,126,142,143,144,116,145,146,119,147,148,149,150,151,152,153,154,155],"builder","artifacts builder","artifacts","web artifacts builder","single-file","css","Tailwind","ui components","single","use","file","components","react","multi","state","routing","state management",{"skillNo":157,"name":158,"nameZh":6,"description":159,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":160,"sourceHtmlUrl":161,"categories":162,"keywords":164,"publishedAt":44},"01M71QD9T2XC3EWMQNN9ACNWQ1","vueuse-functions","Apply VueUse composables where appropriate to build concise, maintainable Vue.js \u002F Nuxt features.","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fvueuse-functions\u002FLICENSE.md","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fvueuse-functions\u002FSKILL.md",[163],{"categoryNo":111,"categoryName":112,"taxonomyType":21,"taxonomyTypeDesc":22},[165,166,167,168,169,170,171,172,173,174,175,176,177,178],"functions","vueuse","vueuse functions","project","nuxt","apply","github","concise","build","https","features","vue","js","composables",{"skillNo":180,"name":181,"nameZh":6,"description":182,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":183,"sourceHtmlUrl":184,"categories":185,"keywords":188,"publishedAt":44},"01M71QD51HWSGYMCMB7RW7PGK8","vue-testing-best-practices","Use for Vue.js testing. Covers Vitest, Vue Test Utils, component testing, mocking, testing patterns, and Playwright for E2E testing.","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fvue-testing-best-practices\u002FLICENSE.md","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fvue-testing-best-practices\u002FSKILL.md",[186,187],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":111,"categoryName":112,"taxonomyType":21,"taxonomyTypeDesc":22},[189,40,190,176,191,125,192,193,194,171,195,116,196,197,198,148,199,177,200],"vue testing best practices","testing best practices","best practices","test","testing patterns","component","vitest","mocking","e2e","utils","patterns","e2e testing",{"skillNo":202,"name":203,"nameZh":6,"description":204,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":205,"sourceHtmlUrl":206,"categories":207,"keywords":209,"publishedAt":44},"01M71QD358H2DJ9THJRBP77T3T","vue-router-best-practices","Vue Router 4 patterns, navigation guards, route params, and route-component lifecycle interactions.","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fvue-router-best-practices\u002FLICENSE.md","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fvue-router-best-practices\u002FSKILL.md",[208],{"categoryNo":111,"categoryName":112,"taxonomyType":21,"taxonomyTypeDesc":22},[210,176,211,191,212,194,213,171,199,214,215],"router","vue router best practices","route","guards","navigation","lifecycle",{"skillNo":217,"name":218,"nameZh":6,"description":219,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":220,"sourceHtmlUrl":221,"categories":222,"keywords":224,"publishedAt":44},"01M71QCYPNWDE2MQZ1TF3ED5S4","vue-best-practices","MUST be used for Vue.js tasks. Strongly recommends Composition API with `\u003Cscript setup>` and TypeScript as the standard approach. Covers Vue 3, SSR, Volar, vue-tsc. Load for any Vue, .vue files, Vue Router, Pinia, or Vite with Vue work. ALWAYS use Composition API unless the project explicitly requires Options API.","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fvue-best-practices\u002FLICENSE.md","https:\u002F\u002Fgithub.com\u002Fpattanation\u002FNorvik\u002Fblob\u002Fclaude\u002Finstall-gsd-build-foQdV\u002F.agents\u002Fskills\u002Fvue-best-practices\u002FSKILL.md",[223],{"categoryNo":111,"categoryName":112,"taxonomyType":21,"taxonomyTypeDesc":22},[225,176,191,226,168,227,228,33,210,229,230,231,232,233,234,116,235,148,236,237],"vue best practices","Composition","files","tasks","pinia","work","options","ssr","typescript","as","vite","load","standard"]