API mocks — MSW (in-process) + Mockoon (out-of-process), split by surface
API mocks — MSW + Mockoon, split by surface
Decision
Two API-mock tools, one per surface — same posture as the AI split between Puter.js and Cloudflare Workers AI: different surfaces, different tools, no overlap.
- In-process mocks — MSW (Mock Service Worker).
Service-Worker based in the browser; Node interceptor in tests.
Used for Vitest unit / integration
suites, Storybook stories
that need network responses, and
pnpm devwhen the developer wants the app to talk to a deterministic stub instead of a live upstream. - Out-of-process mocks — Mockoon. Free OSS desktop app + headless CLI. Used for E2E ( Playwright) suites against third-party APIs the family doesn't own (Razorpay sandbox, Open-Meteo, Alpha Vantage when offline / quota-conscious), and for manual exploratory work.
Both free, both OSS, both no card.
Why
- No single tool fits both surfaces. MSW's worker model is
perfect when the code-under-test is the family's own JS — it
intercepts inside the same process, so requests / matchers / handlers
all live in TypeScript next to the test. Mockoon's process model
is perfect when the code-under-test is the deployed Hono Worker
hitting a third-party API — Mockoon stands up a separate HTTP
server on
localhost:3001, the Worker'sBASE_URLenv points at it, and the entire end-to-end pipeline exercises against it. - Cost is zero on both. MSW is MIT OSS via npm. Mockoon is MIT OSS — free desktop app, free CLI, no account, no telemetry by default.
- Reuses substrate already required by other family decisions.
MSW slots into the existing Vitest setup
- Storybook preview the
testing three-layer decision already
ships in
@chirag127/oriz-kit/testing/. Mockoon CLI runs from the same GitHub Actions schedule pattern any other CI step uses.
- Storybook preview the
testing three-layer decision already
ships in
- Cuts external-API load, which preserves quotas. Pointing E2E at a Mockoon mock instead of the live Alpha Vantage endpoint keeps the 25 req/day budget for production traffic — same rationale as the CF Worker quota mitigation playbook: burn cheap synthetic substrate first, real quota last.
Implications
Where each lives
@chirag127/oriz-kit/testing/
+-- msw/
¦ +-- handlers.ts ? shared MSW handlers (Razorpay sandbox, Hono RPC, Firestore REST)
¦ +-- server.ts ? Node-side `setupServer()` for Vitest
¦ +-- browser.ts ? Service-Worker `setupWorker()` for Storybook + dev
+-- mockoon/
¦ +-- razorpay.json ? Mockoon environment file — Razorpay sandbox endpoints
¦ +-- open-meteo.json ? weather data API — used when offline
¦ +-- alpha-vantage.json ? finance data API — used when offline / quota-conscious
¦ +-- README.md
Per-site mocks/ directory only carries site-specific handlers;
the shared core ships from oriz-kit.
MSW surfaces
- Vitest unit tests —
setupServer()started invitest.setup.ts, handlers reset between tests. - Storybook —
setupWorker()registered in.storybook/preview.ts; per-storyparameters.msw.handlersoverrides as needed (Storybook v7+ MSW addon). pnpm dev— opt-in viaVITE_USE_MSW=truein dev.env; the worker registers fromsrc/main.tsxonly when the flag is on.- Playwright E2E (route-level) — Playwright's
page.route()is preferred for E2E intercepts when the test needs to mock the browser's outbound requests; MSW is reserved for in-process surfaces.
Mockoon surfaces
- E2E against deployed Hono Worker —
wrangler devreadsBASE_URL_RAZORPAY=http://localhost:3001(or whatever Mockoon binds to); Playwright drives the local preview while Mockoon CLI serves the third-party shape on a sibling port. - Manual dev when an upstream is rate-limited / down — developer launches the Mockoon GUI, opens the Razorpay environment, hits "Start", and points the local Worker at it.
- CI E2E job — Mockoon CLI (
@mockoon/cli) runs in a sibling step on the GitHub Actions runner, the Hono Worker's preview URL points at it, Playwright runs the suite. No Razorpay sandbox keys leak into CI; no Alpha Vantage budget burned.
What we don't mock with these
- Firestore via REST — keep the live Firebase emulator if it's already on hand (
firebase emulators:start --only firestore); MSW is a fallback for unit-test surfaces that don't want to spin the emulator. The firebase-rest-firestore decision already locks the Worker-side approach; mocks at the boundary (the REST endpoint) are MSW's job. - Cloudflare Workers AI — has a built-in
--localmode inwranglerthat uses simulated weights / dummy responses; we prefer that to MSW.
What we don't add
- No paid mock-server — Mocky.io paid tier, Postman Mock Server paid tier, Beeceptor paid tier all rejected — fights the no-paid-tier posture.
- No SaaS mock service — even a hypothetical free Postman Mocks tier adds another vendor surface for capability already covered by two OSS tools.
- No hand-rolled
fetchstubs in test files — handlers always live in the sharedmsw/handlers.tsso coverage stays consistent across unit + Storybook + dev surfaces.
Per-PR gating
- MSW handlers + Mockoon JSON files are version-controlled; PR changes get reviewed by CodeRabbit alongside the rest of the diff.
- A drift between an updated upstream API shape and the mock is caught by the test layers — Vitest (MSW) and Playwright (Mockoon) both run on every PR.
Cross-refs
- MSW service entry
- Mockoon service entry
- Testing three-layer decision
- Vitest service entry
- Playwright service entry
- Storybook service entry
- testing services index
- Razorpay decision (sandbox is the most-mocked upstream)
- Open-Meteo + Alpha Vantage decision (Mockoon's other big customers)
- No card-on-file rule
- No subscriptions rule