{"post":{"slug":"how-to-mock-an-api-2026","title":"How to mock an API in 2026","excerpt":"Mocking APIs lets you develop and test in isolation without hitting real services. This 2026 guide covers MSW, local stubs, and contract testing.","category":"code","date":"April 1, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">M</span>ocking an API is not just a testing convenience — it is a development multiplier. Frontend teams should not block on backend teams; test suites should not make real HTTP requests. In 2026 the tooling for API mocking is mature, ergonomic, and consistent across browser and Node environments.\n\n## What changed in 2026\n\n- **MSW v2 unified browser and Node.** The same handlers work in a browser service worker, Node tests (Vitest/Jest), and the new Bun runtime. No more duplicated mock logic.\n- **OpenAPI is everywhere.** Most teams generate stubs automatically from their spec using `@stoplight/prism-cli`, eliminating hand-written mock data.\n- **Contract testing went mainstream.** Pact and OpenAPI-based contract tests run in CI and catch API drift before it reaches production.\n- **Edge runtimes changed test environments.** Cloudflare Workers and Deno use the Web Fetch API natively; MSW's HTTP handler approach fits them without polyfills.\n\n## The three layers of API mocking\n\n| Layer | Tool | Best for |\n| --- | --- | --- |\n| Network interception | MSW | Unit and integration tests |\n| Local HTTP stub | json-server, Prism | Frontend dev without backend |\n| Record + replay | Polly.js, nock.back | Slow/paid third-party APIs |\n\n## MSW: network-level mocking\n\nInstall once, use everywhere:\n\n```bash\nnpm install -D msw\n```\n\nDefine handlers:\n\n```typescript\n// src/mocks/handlers.ts\nimport { http, HttpResponse } from \"msw\";\n\nexport const handlers = [\n  http.get(\"/api/products\", () => {\n    return HttpResponse.json([\n      { id: \"1\", name: \"Widget\", price: 9.99 },\n      { id: \"2\", name: \"Gadget\", price: 24.99 },\n    ]);\n  }),\n\n  http.post(\"/api/orders\", async ({ request }) => {\n    const body = await request.json();\n    return HttpResponse.json({ id: \"ord_123\", ...body }, { status: 201 });\n  }),\n\n  // Simulate an error\n  http.get(\"/api/products/:id\", ({ params }) => {\n    if (params.id === \"999\") {\n      return new HttpResponse(null, { status: 404 });\n    }\n    return HttpResponse.json({ id: params.id, name: \"Widget\" });\n  }),\n];\n```\n\nNode test setup (Vitest/Jest):\n\n```typescript\n// src/mocks/server.ts\nimport { setupServer } from \"msw/node\";\nimport { handlers } from \"./handlers\";\n\nexport const server = setupServer(...handlers);\n```\n\nBrowser dev setup:\n\n```typescript\n// src/mocks/browser.ts\nimport { setupWorker } from \"msw/browser\";\nimport { handlers } from \"./handlers\";\n\nexport const worker = setupWorker(...handlers);\n\n// main.tsx\nif (import.meta.env.DEV) {\n  const { worker } = await import(\"./mocks/browser\");\n  await worker.start({ onUnhandledRequest: \"warn\" });\n}\n```\n\n## Prism: OpenAPI-driven stubs\n\nIf you have an OpenAPI spec, Prism generates a fully working stub server in seconds:\n\n```bash\nnpx @stoplight/prism-cli mock openapi.yaml\n# Stub running on http://localhost:4010\n```\n\nPrism validates requests against your schema and returns example responses from the spec. Zero hand-written mock data required.\n\n## json-server: rapid local REST API\n\nFor CRUD prototyping before the backend exists:\n\n```bash\nnpm install -D json-server\n```\n\n`db.json`:\n\n```json\n{\n  \"users\": [{ \"id\": 1, \"name\": \"Alice\" }],\n  \"posts\": [{ \"id\": 1, \"userId\": 1, \"title\": \"Hello\" }]\n}\n```\n\n```bash\nnpx json-server db.json --port 3001\n# GET /users, POST /users, PUT /users/1, DELETE /users/1 — all work\n```\n\n## How to pick a mocking strategy\n\n1. **Writing unit or integration tests?** → MSW in Node mode.\n2. **Developing frontend without backend?** → MSW in browser mode or Prism.\n3. **OpenAPI spec exists?** → Prism for instant stub server.\n4. **Third-party API that costs money or is slow?** → Record real responses with Polly.js, replay in CI.\n5. **Need to validate API contracts don't drift?** → Add Pact or Dredd to CI.\n\n## Contract testing to keep mocks honest\n\nThe biggest risk with mocks: they diverge from reality. A contract test runs against the real API and asserts the shape you mocked still matches:\n\n```typescript\n// pact.test.ts (consumer side)\nimport { PactV3, MatchersV3 } from \"@pact-foundation/pact\";\n\nconst provider = new PactV3({ consumer: \"Frontend\", provider: \"OrdersAPI\" });\n\nit(\"returns an order\", async () => {\n  await provider\n    .addInteraction({\n      uponReceiving: \"a request for order 1\",\n      withRequest: { method: \"GET\", path: \"/api/orders/1\" },\n      willRespondWith: {\n        status: 200,\n        body: { id: MatchersV3.string(), total: MatchersV3.number() },\n      },\n    })\n    .executeTest(async (mockServer) => {\n      const res = await fetch(`${mockServer.url}/api/orders/1`);\n      expect(res.status).toBe(200);\n    });\n});\n```\n\n## Common mistakes\n\n**Never resetting handlers between tests.** One test's `server.use(...)` override bleeds into the next. Always call `server.resetHandlers()` in `afterEach`.\n\n**Mocking modules instead of the network.** `vi.mock(\"axios\")` is fragile; it breaks when you swap HTTP clients. Mock the network; let the client code run for real.\n\n**Static mock data that never exercises edge cases.** Write handlers for 404, 429, 500, and empty arrays — not just the happy path.\n\n**Over-mocking in E2E tests.** End-to-end tests should hit the real API. Mocking in E2E defeats the purpose.\n\n## What to skip\n\n- **Manual `fetch` global overrides** — they're global state and cause hard-to-debug test pollution.\n- **WireMock** for pure JS/TS stacks — MSW does everything WireMock does without spinning up a JVM.\n- **Copying mock data manually between test files** — centralise handlers in `src/mocks/handlers.ts` and import everywhere.\n\n## FAQ\n\n**Does MSW work with `axios` or `ky`?**\nYes — MSW intercepts at the network level, not the library level. Any HTTP client that uses `XMLHttpRequest` or `fetch` is intercepted automatically.\n\n**Can I use MSW in Storybook?**\nYes — Storybook has official MSW support via `storybook-addon-mock` or by importing `worker.start()` in your Storybook preview file.\n\n**How do I simulate slow responses?**\nAdd a delay in your MSW handler: `await new Promise(r => setTimeout(r, 2000))` before returning the response.\n\n**What is the difference between a stub and a mock?**\nA stub returns canned data; it doesn't verify behaviour. A mock also asserts how it was called (e.g. that a POST was made with specific params). MSW is primarily a stub; add `vi.spyOn` or Pact for verification.\n\n## Where to go next\n\n- [How to handle API errors in 2026](/blog/how-to-handle-api-errors-2026)\n- [How to test a React component in 2026](/blog/how-to-test-a-react-component-2026)\n- [How to build a REST API in 2026](/blog/how-to-build-a-rest-api-2026)","hue":321,"metaTitle":"How to Mock an API in 2026 (MSW, Stubs, Contracts)","metaDescription":"2026 guide to API mocking: MSW for tests, json-server for local dev, Prism for OpenAPI stubs, and contract testing to keep mocks honest.","keywords":["how to mock an api 2026","api mocking 2026","msw mock service worker 2026","mock api for testing 2026","json-server 2026","contract testing 2026","prism openapi mock 2026","api stub development 2026","testing without real api 2026","mock fetch requests 2026"],"tldr":["**MSW (Mock Service Worker) is the 2026 standard** — intercepts at the network layer, works in browser and Node, no brittle module mocks.","**Use contract tests** to keep mocks honest — if the real API changes, the contract test catches the drift.","**Local stubs with `json-server` or `Prism`** speed up frontend development when the backend is not ready.","**Record and replay** with tools like Polly.js or `nock.back` for slow or expensive third-party APIs.","**Skip:** `jest.mock(\"node-fetch\")` or global `fetch` overrides — they leak between tests and hide real network behaviour."],"readTime":"4 min read","wordCount":960},"related":[{"slug":"api-mocking-tools-compared-2026","title":"API Mocking Tools Compared 2026: Which to Pick","excerpt":"Mocking an API ranges from an in-test stub to a spec-driven, stateful fake server, and the tools behind each option differ a lot. Here is how Mockoon, Prism, WireMock, MSW, and json-server compare in 2026.","category":"code","date":"July 28, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":300},{"slug":"what-is-an-api-mock-server-2026","title":"What is an API mock server in 2026? A fake backend on demand","excerpt":"An API mock server is a real, running server that simulates an API without the real backend behind it, useful for parallel development, demos, and tests that should never touch production. Here is how it fits in 2026.","category":"code","date":"July 21, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":310},{"slug":"mocking-explained-2026","title":"Mocking explained in 2026: when to mock, when to fake, when to stop","excerpt":"Mocking is both the most useful and most abused technique in testing. This 2026 guide explains mocks vs fakes vs stubs, and when each belongs in your test suite.","category":"code","date":"April 20, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":306}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"how-to-merge-on-the-highway-2026","title":"How to merge on the highway in 2026: the technique that keeps traffic flowing","excerpt":"Bad highway merges cause crashes and traffic jams that ripple for miles. This 2026 guide explains the correct technique, the zipper method, and common mistakes.","category":"guides","date":"April 1, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":310},"older":{"slug":"how-to-monitor-a-service-2026","title":"How to monitor a service in 2026: metrics, logs, traces, and alerts that work","excerpt":"A service without observability is a black box you only look inside after something breaks. Here is the 2026 guide to metrics, logs, traces, and alerts.","category":"code","date":"April 1, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":319}}