Claude Code से MSW API Mock बनाने की व्यावहारिक गाइड
Claude Code और MSW से ब्राउज़र, Node टेस्ट, auth, error और CI के लिए भरोसेमंद API Mock बनाएं।
MSW का पूरा नाम Mock Service Worker है। ब्राउज़र में यह Service Worker के जरिए HTTP अनुरोधों को रोकता है, और Node.js टेस्ट में उसी प्रक्रिया के अनुरोध मॉड्यूल को रोकता है। इसका फायदा यह है कि स्थानीय विकास, Vitest और CI में वही API handler इस्तेमाल किए जा सकते हैं।
Claude Code के साथ MSW इस्तेमाल करते समय लक्ष्य सिर्फ स्थिर JSON लौटाना नहीं होना चाहिए। उपयोगी mock में प्रमाणीकरण, पेजिंग, इनपुट जांच, त्रुटि स्थिति, नेटवर्क विफलता और API अनुबंध में बदलाव की जांच शामिल होनी चाहिए। सिर्फ 200 OK लौटाने वाला mock तेज लगता है, पर वह असली UI समस्याएं छिपा देता है।
यह लेख MSW 2 के आधिकारिक दस्तावेजों के अनुसार http, HttpResponse, setupWorker और setupServer का उपयोग करता है। पहले MSW Quick start देखें, फिर Browser integration और Node.js integration। विफलता मामलों के लिए error responses और network errors उपयोगी हैं।
आगे पढ़ने के लिए Vitest उन्नत तकनीक, Playwright E2E टेस्ट, API टेस्ट स्वचालन और CI/CD सेटअप देखें।
उपयोग के मामले
| मामला | क्या simulate करना है | न करने का जोखिम |
|---|---|---|
| backend से पहले UI विकास | सूची, विवरण, बनाना, खाली स्थिति | असली API जोड़ते समय field mismatch |
| auth और भूमिका जांच | 401, 403, role आधारित response | सामान्य user को admin action दिखना |
| विफलता UX | 500, 422, network failure, delay | loading खत्म न होना या retry button गायब |
| CI में contract guard | JSON shape, जरूरी field, status | API change बिना चेतावनी production में जाना |
Claude Code को साफ निर्देश दें:
MSW 2 से users API mock बनाएं।
Browser development और Vitest Node environment एक ही handlers.ts साझा करें।
Required auth, pagination, role filter, 422, 404, 500 और network-error test शामिल करें।
TypeScript इस्तेमाल करें और कोई undefined application type न छोड़ें।
संरचना
flowchart LR
UI["ब्राउज़र UI"] --> Worker["setupWorker"]
Test["Vitest / CI"] --> Server["setupServer"]
Worker --> Handlers["MSW handlers.ts"]
Server --> Handlers
Handlers --> Contract["API contract: status / JSON / auth / delay"]
स्थापना
npm i -D msw vitest typescript
npx msw init public/ --save
स्थानीय server चलाते समय http://localhost:5173/mockServiceWorker.js खोलें। अगर 404 मिलता है, तो ब्राउज़र में request intercept नहीं होगी।
सीधे उपयोग योग्य handler
यह उदाहरण user list, detail, create, update और delete देता है। इसमें auth, pagination, validation, 404 और delay है। URL absolute है ताकि Node के native fetch के साथ भी चले।
import { delay, http, HttpResponse } from "msw";
export const API_ORIGIN = "https://api.example.test";
type Role = "admin" | "editor" | "viewer";
export type User = {
id: string;
name: string;
email: string;
role: Role;
};
type CreateUserInput = {
name: string;
email: string;
role?: Role;
};
type ErrorBody = {
error: {
code: string;
message: string;
requestId: string;
};
};
type PageMeta = {
total: number;
page: number;
perPage: number;
};
type UserListResponse = {
data: User[];
meta: PageMeta;
};
const seedUsers: User[] = [
{ id: "u_1", name: "Aki Tanaka", email: "aki@example.com", role: "admin" },
{ id: "u_2", name: "Bea Sato", email: "bea@example.com", role: "editor" },
{ id: "u_3", name: "Cal Mori", email: "cal@example.com", role: "viewer" },
];
let users: User[] = [...seedUsers];
const jsonError = (status: number, code: string, message: string) =>
HttpResponse.json(
{ error: { code, message, requestId: "req_mock_001" } },
{ status }
);
const requireAuth = (request: Request) => {
const token = request.headers.get("authorization");
return token === "Bearer demo-token"
? null
: jsonError(401, "UNAUTHORIZED", "Missing or invalid bearer token");
};
const isRole = (value: string | null): value is Role =>
value === "admin" || value === "editor" || value === "viewer";
export function resetMockData() {
users = [...seedUsers];
}
export const handlers = [
http.get(`${API_ORIGIN}/users`, async ({ request }) => {
const authError = requireAuth(request);
if (authError) return authError;
await delay(120);
const url = new URL(request.url);
const page = Number(url.searchParams.get("page") ?? "1");
const perPage = Number(url.searchParams.get("perPage") ?? "20");
const role = url.searchParams.get("role");
if (!Number.isInteger(page) || page < 1) {
return jsonError(422, "INVALID_PAGE", "page must be a positive integer");
}
if (!Number.isInteger(perPage) || perPage < 1 || perPage > 50) {
return jsonError(422, "INVALID_PER_PAGE", "perPage must be between 1 and 50");
}
if (role && !isRole(role)) {
return jsonError(422, "INVALID_ROLE", "role must be admin, editor, or viewer");
}
const filtered = role ? users.filter((user) => user.role === role) : users;
const start = (page - 1) * perPage;
return HttpResponse.json({
data: filtered.slice(start, start + perPage),
meta: { total: filtered.length, page, perPage },
});
}),
http.get(`${API_ORIGIN}/users/:id`, async ({ params, request }) => {
const authError = requireAuth(request);
if (authError) return authError;
await delay(80);
const user = users.find((item) => item.id === String(params.id));
return user
? HttpResponse.json({ data: user })
: jsonError(404, "USER_NOT_FOUND", "User was not found");
}),
http.post(`${API_ORIGIN}/users`, async ({ request }) => {
const authError = requireAuth(request);
if (authError) return authError;
const body = (await request.json()) as Partial<CreateUserInput>;
if (!body.name?.trim() || !body.email?.includes("@")) {
return jsonError(422, "INVALID_INPUT", "name and a valid email are required");
}
if (body.role && !isRole(body.role)) {
return jsonError(422, "INVALID_ROLE", "role must be admin, editor, or viewer");
}
const user: User = {
id: `u_${Date.now()}`,
name: body.name.trim(),
email: body.email,
role: body.role ?? "viewer",
};
users = [user, ...users];
return HttpResponse.json({ data: user }, { status: 201 });
}),
http.patch(`${API_ORIGIN}/users/:id`, async ({ params, request }) => {
const authError = requireAuth(request);
if (authError) return authError;
const index = users.findIndex((item) => item.id === String(params.id));
if (index === -1) return jsonError(404, "USER_NOT_FOUND", "User was not found");
const body = (await request.json()) as Partial<CreateUserInput>;
if (body.email && !body.email.includes("@")) {
return jsonError(422, "INVALID_EMAIL", "email must include @");
}
if (body.role && !isRole(body.role)) {
return jsonError(422, "INVALID_ROLE", "role must be admin, editor, or viewer");
}
users[index] = { ...users[index], ...body };
return HttpResponse.json({ data: users[index] });
}),
http.delete(`${API_ORIGIN}/users/:id`, async ({ params, request }) => {
const authError = requireAuth(request);
if (authError) return authError;
users = users.filter((item) => item.id !== String(params.id));
return new HttpResponse(null, { status: 204 });
}),
];
ब्राउज़र setup
ब्राउज़र में setupWorker इस्तेमाल करें और app render करने से पहले worker.start() का इंतजार करें।
import { setupWorker } from "msw/browser";
import { handlers } from "./handlers";
export const worker = setupWorker(...handlers);
import React from "react";
import ReactDOM from "react-dom/client";
import { App } from "./App";
async function enableMocking() {
if (!import.meta.env.DEV || import.meta.env.VITE_API_MOCKING !== "enabled") {
return;
}
const { worker } = await import("./mocks/browser");
await worker.start({
onUnhandledRequest: "bypass",
});
}
enableMocking().then(() => {
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
});
इसे VITE_API_MOCKING=enabled npm run dev से स्पष्ट रूप से चालू करें। production में login, purchase, form और revenue CTA को असली services से ही बात करनी चाहिए।
Vitest और CI
import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest";
import { http, HttpResponse } from "msw";
import { setupServer } from "msw/node";
import { API_ORIGIN, handlers, resetMockData } from "../src/mocks/handlers";
const server = setupServer(...handlers);
function authed(input: string, init: RequestInit = {}) {
const headers = new Headers(init.headers);
headers.set("authorization", "Bearer demo-token");
return fetch(input, { ...init, headers });
}
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => {
server.resetHandlers();
resetMockData();
});
afterAll(() => server.close());
describe("users API mock", () => {
it("returns a paginated user list", async () => {
const response = await authed(`${API_ORIGIN}/users?page=1&perPage=2`);
const body = (await response.json()) as {
data: Array<Record<string, unknown>>;
meta: Record<string, unknown>;
};
expect(response.status).toBe(200);
expect(body.data).toHaveLength(2);
expect(body.meta).toMatchObject({ total: 3, page: 1, perPage: 2 });
});
it("rejects missing auth", async () => {
const response = await fetch(`${API_ORIGIN}/users`);
const body = (await response.json()) as { error: { code: string } };
expect(response.status).toBe(401);
expect(body.error.code).toBe("UNAUTHORIZED");
});
it("simulates a network failure for retry UI", async () => {
server.use(
http.get(`${API_ORIGIN}/users`, () => {
return HttpResponse.error();
})
);
await expect(authed(`${API_ORIGIN}/users`)).rejects.toThrow();
});
it("guards against response contract drift", async () => {
const response = await authed(`${API_ORIGIN}/users`);
const body = (await response.json()) as {
data: Array<Record<string, unknown>>;
meta: Record<string, unknown>;
};
expect(Object.keys(body.data[0]).sort()).toEqual(["email", "id", "name", "role"]);
expect(body.data[0]).toEqual(
expect.objectContaining({
id: expect.any(String),
email: expect.stringContaining("@"),
})
);
expect(body.meta).toEqual(expect.objectContaining({ page: 1, perPage: 20 }));
});
});
name: msw-contract
on:
pull_request:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run test -- --run
सामान्य गलतियां
पहली गलती है mockServiceWorker.js प्रकाशित न होना। अगर यह 404 है, तो ब्राउज़र में intercept नहीं होगा।
दूसरी गलती है test state leak होना। हर test के बाद server.resetHandlers() और data reset करें।
तीसरी गलती है CI में onUnhandledRequest: "bypass" रखना। test में unhandled request failure होना चाहिए।
चौथी गलती है auth को simulate न करना। असली bug valid session, expired session, missing permission और wrong role के बीच आते हैं।
पांचवीं गलती है contract न जांचना। सिर्फ HTTP status नहीं, बल्कि data, meta.total और error.code भी जांचें।
monetization CTA
MSW revenue paths पर खास उपयोगी है: article CTA, product purchase, contact form, free signup और checkout preview। publish से पहले 500, धीमा response, expired auth और validation error simulate करें। Claude Code prompt और review checklist को reusable बनाना हो तो products या Claude Code training देखें।
वास्तविक परिणाम
Masa ने इस संरचना को article CTA और product flow पर आजमाया। सबसे उपयोगी चीजें HttpResponse.error() और onUnhandledRequest: "error" रहीं। सिर्फ success mock होने पर retry button गायब होना, auth header छूटना और meta.total हटना पकड़ा नहीं गया। वही handlers local development और CI में साझा करने से failure दोहराए जा सके और Claude Code से सुधार कराना आसान हुआ।
मुफ़्त PDF: Claude Code cheatsheet
Email डालें और commands, review habits तथा safe workflow वाली एक-page PDF पाएँ.
हम आपका data सुरक्षित रखते हैं और spam नहीं भेजते.
लेखक के बारे में
Masa
Claude Code workflow और team adoption पर काम करने वाला engineer.
संबंधित लेख
Claude Code permission safety ladder: access धीरे-धीरे बढ़ाएं
read-only से limited edits, proof commands और deploy checks तक permission बढ़ाने की सुरक्षित ladder.
Claude Code Small PR Proof Pack: छोटे PR को review-ready बनाना
Claude Code PR के लिए diff, checks, public URL, CTA path और rollback वाला practical proof pack.
Claude Code Review Gate Before Commit: diff, test, public URL और CTA जांच
Claude Code से commit से पहले review gate बनाएं: diff, build, public URL, Gumroad, consultation, tests और unrelated files।