Mocks APIs with Mock Service Worker including request handlers, server/browser setup, and testing integration. Use when mocking APIs in tests, developing without backend, or simulating network conditions.
Sets up Mock Service Worker to intercept network requests in tests and development. Claude will use this when you need to mock APIs, test without a backend, or simulate network conditions in Node.js or browser environments.
/plugin marketplace add mgd34msu/goodvibes-plugin/plugin install goodvibes@goodvibes-marketThis skill inherits all available tools. When active, it can use any tool Claude has access to.
API mocking library for browser and Node.js with network-level interception.
Install:
npm install msw --save-dev
Project structure:
src/
mocks/
handlers.ts # Request handlers
browser.ts # Browser setup
server.ts # Node.js setup
// src/mocks/handlers.ts
import { http, HttpResponse } from 'msw';
export const handlers = [
// GET request
http.get('/api/users', () => {
return HttpResponse.json([
{ id: 1, name: 'John Doe' },
{ id: 2, name: 'Jane Smith' },
]);
}),
// GET with params
http.get('/api/users/:id', ({ params }) => {
const { id } = params;
return HttpResponse.json({
id: Number(id),
name: 'John Doe',
email: 'john@example.com',
});
}),
// POST request
http.post('/api/users', async ({ request }) => {
const body = await request.json();
return HttpResponse.json(
{ id: 3, ...body },
{ status: 201 }
);
}),
// PUT request
http.put('/api/users/:id', async ({ params, request }) => {
const { id } = params;
const body = await request.json();
return HttpResponse.json({ id: Number(id), ...body });
}),
// DELETE request
http.delete('/api/users/:id', ({ params }) => {
return new HttpResponse(null, { status: 204 });
}),
// PATCH request
http.patch('/api/users/:id', async ({ params, request }) => {
const { id } = params;
const body = await request.json();
return HttpResponse.json({ id: Number(id), ...body });
}),
];
http.get('/api/search', ({ request }) => {
const url = new URL(request.url);
const query = url.searchParams.get('q');
const page = url.searchParams.get('page') || '1';
return HttpResponse.json({
query,
page: Number(page),
results: [
{ id: 1, title: `Result for "${query}"` },
],
});
}),
http.get('/api/protected', ({ request }) => {
const authHeader = request.headers.get('Authorization');
if (!authHeader?.startsWith('Bearer ')) {
return HttpResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
return HttpResponse.json({ data: 'protected data' });
}),
http.get('/api/data', () => {
return HttpResponse.json(
{ data: 'value' },
{
headers: {
'X-Custom-Header': 'custom-value',
'Cache-Control': 'no-cache',
},
}
);
}),
// src/mocks/server.ts
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);
// src/test/setup.ts
import { beforeAll, afterEach, afterAll } from 'vitest';
import { server } from '../mocks/server';
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
// vitest.config.ts
export default defineConfig({
test: {
setupFiles: ['./src/test/setup.ts'],
},
});
// src/test/setup.ts
import { server } from '../mocks/server';
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
// jest.config.js
module.exports = {
setupFilesAfterEnv: ['./src/test/setup.ts'],
};
npx msw init public/ --save
// src/mocks/browser.ts
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';
export const worker = setupWorker(...handlers);
// src/main.tsx or src/index.tsx
async function enableMocking() {
if (process.env.NODE_ENV !== 'development') {
return;
}
const { worker } = await import('./mocks/browser');
return worker.start({
onUnhandledRequest: 'bypass',
});
}
enableMocking().then(() => {
ReactDOM.createRoot(document.getElementById('root')!).render(
<App />
);
});
// src/mocks/index.ts
export async function initMocks() {
if (typeof window === 'undefined') {
const { server } = await import('./server');
server.listen();
} else {
const { worker } = await import('./browser');
await worker.start();
}
}
// app/providers.tsx
'use client';
import { useEffect, useState } from 'react';
export function Providers({ children }: { children: React.ReactNode }) {
const [mockingEnabled, setMockingEnabled] = useState(false);
useEffect(() => {
async function enableMocking() {
if (process.env.NODE_ENV === 'development') {
const { initMocks } = await import('@/mocks');
await initMocks();
}
setMockingEnabled(true);
}
enableMocking();
}, []);
if (!mockingEnabled) {
return null;
}
return <>{children}</>;
}
import { graphql, HttpResponse } from 'msw';
export const handlers = [
// Query
graphql.query('GetUser', ({ variables }) => {
const { id } = variables;
return HttpResponse.json({
data: {
user: {
id,
name: 'John Doe',
email: 'john@example.com',
},
},
});
}),
// Mutation
graphql.mutation('CreateUser', ({ variables }) => {
const { input } = variables;
return HttpResponse.json({
data: {
createUser: {
id: '123',
...input,
},
},
});
}),
// With custom endpoint
graphql.link('https://api.example.com/graphql').query('GetPosts', () => {
return HttpResponse.json({
data: {
posts: [
{ id: '1', title: 'Hello World' },
],
},
});
}),
];
import { http, HttpResponse } from 'msw';
import { server } from '../mocks/server';
test('handles error response', async () => {
server.use(
http.get('/api/users', () => {
return HttpResponse.json(
{ error: 'Internal Server Error' },
{ status: 500 }
);
})
);
// Test error handling
render(<UserList />);
await screen.findByText('Error loading users');
});
test('handles empty response', async () => {
server.use(
http.get('/api/users', () => {
return HttpResponse.json([]);
})
);
render(<UserList />);
await screen.findByText('No users found');
});
import { delay, http, HttpResponse } from 'msw';
http.get('/api/users', async () => {
await delay(1000); // 1 second delay
return HttpResponse.json([{ id: 1, name: 'John' }]);
}),
// Infinite delay (for testing loading states)
http.get('/api/data', async () => {
await delay('infinite');
return HttpResponse.json({ data: 'never returns' });
}),
import { http, HttpResponse } from 'msw';
http.get('/api/users', () => {
return HttpResponse.error();
}),
import { http, passthrough } from 'msw';
http.get('/api/analytics', () => {
return passthrough();
}),
import { http, HttpResponse } from 'msw';
import { server } from '../mocks/server';
test('sends correct data', async () => {
let requestBody: any;
server.use(
http.post('/api/users', async ({ request }) => {
requestBody = await request.json();
return HttpResponse.json({ id: 1 });
})
);
// Trigger the request
await createUser({ name: 'John', email: 'john@example.com' });
// Assert request body
expect(requestBody).toEqual({
name: 'John',
email: 'john@example.com',
});
});
http.get('/api/session', ({ cookies }) => {
const sessionId = cookies.sessionId;
if (!sessionId) {
return HttpResponse.json(
{ error: 'No session' },
{ status: 401 }
);
}
return HttpResponse.json({ user: 'John' });
}),
// Set cookies in response
http.post('/api/login', () => {
return HttpResponse.json(
{ success: true },
{
headers: {
'Set-Cookie': 'sessionId=abc123; Path=/; HttpOnly',
},
}
);
}),
import { http, HttpResponse } from 'msw';
http.get('/api/stream', () => {
const encoder = new TextEncoder();
const stream = new ReadableStream({
start(controller) {
controller.enqueue(encoder.encode('Hello'));
controller.enqueue(encoder.encode(' '));
controller.enqueue(encoder.encode('World'));
controller.close();
},
});
return new HttpResponse(stream, {
headers: {
'Content-Type': 'text/plain',
},
});
}),
| Mistake | Fix |
|---|---|
| Forgetting resetHandlers | Add to afterEach |
| Wrong handler order | Specific routes before generic |
| Missing async/await | Await request.json() |
| Hardcoded URLs | Use relative or env vars |
| Not initializing worker | Call worker.start() |
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.