api-mock-server

Create realistic mock APIs for testing and development.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "api-mock-server" with this command: npx skills add monkey1sai/openai-cli/monkey1sai-openai-cli-api-mock-server

API Mock Server

Create realistic mock APIs for testing and development.

Core Workflow

  • Choose approach: MSW, json-server, custom

  • Define handlers: Mock endpoints

  • Setup fixtures: Test data

  • Configure scenarios: Success/error states

  • Integrate tests: Use in test suites

  • Document mocks: API contract

MSW (Mock Service Worker)

Installation

npm install -D msw npx msw init ./public --save

Handler Definition

// mocks/handlers.ts import { http, HttpResponse, delay } from 'msw';

// Types interface User { id: string; name: string; email: string; role: 'admin' | 'user'; }

interface CreateUserInput { name: string; email: string; }

// Fixtures const users: User[] = [ { id: '1', name: 'John Doe', email: 'john@example.com', role: 'admin' }, { id: '2', name: 'Jane Smith', email: 'jane@example.com', role: 'user' }, ];

// Handlers export const handlers = [ // GET /api/users http.get('/api/users', async () => { await delay(100); return HttpResponse.json(users); }),

// GET /api/users/:id http.get('/api/users/:id', async ({ params }) => { await delay(50); const user = users.find((u) => u.id === params.id);

if (!user) {
  return new HttpResponse(null, { status: 404 });
}

return HttpResponse.json(user);

}),

// POST /api/users http.post('/api/users', async ({ request }) => { await delay(100); const body = (await request.json()) as CreateUserInput;

const newUser: User = {
  id: String(users.length + 1),
  name: body.name,
  email: body.email,
  role: 'user',
};

users.push(newUser);

return HttpResponse.json(newUser, { status: 201 });

}),

// PUT /api/users/:id http.put('/api/users/:id', async ({ params, request }) => { await delay(50); const body = (await request.json()) as Partial<User>; const index = users.findIndex((u) => u.id === params.id);

if (index === -1) {
  return new HttpResponse(null, { status: 404 });
}

users[index] = { ...users[index], ...body };

return HttpResponse.json(users[index]);

}),

// DELETE /api/users/:id http.delete('/api/users/:id', async ({ params }) => { await delay(50); const index = users.findIndex((u) => u.id === params.id);

if (index === -1) {
  return new HttpResponse(null, { status: 404 });
}

users.splice(index, 1);

return new HttpResponse(null, { status: 204 });

}), ];

Setup for Browser

// mocks/browser.ts import { setupWorker } from 'msw/browser'; import { handlers } from './handlers';

export const worker = setupWorker(...handlers);

// main.tsx or app entry async function enableMocking() { if (process.env.NODE_ENV === 'development') { const { worker } = await import('./mocks/browser'); return worker.start({ onUnhandledRequest: 'bypass', }); } }

enableMocking().then(() => { // Render app });

Setup for Node (Tests)

// mocks/server.ts import { setupServer } from 'msw/node'; import { handlers } from './handlers';

export const server = setupServer(...handlers);

// tests/setup.ts (Jest or Vitest) import { beforeAll, afterEach, afterAll } from 'vitest'; import { server } from '../mocks/server';

beforeAll(() => server.listen({ onUnhandledRequest: 'error' })); afterEach(() => server.resetHandlers()); afterAll(() => server.close());

Test-Specific Handlers

// tests/users.test.ts import { http, HttpResponse } from 'msw'; import { server } from '../mocks/server'; import { render, screen, waitFor } from '@testing-library/react'; import { UserList } from '../components/UserList';

describe('UserList', () => { it('displays users', async () => { render(<UserList />);

await waitFor(() => {
  expect(screen.getByText('John Doe')).toBeInTheDocument();
});

});

it('handles empty state', async () => { server.use( http.get('/api/users', () => { return HttpResponse.json([]); }) );

render(&#x3C;UserList />);

await waitFor(() => {
  expect(screen.getByText('No users found')).toBeInTheDocument();
});

});

it('handles server error', async () => { server.use( http.get('/api/users', () => { return new HttpResponse(null, { status: 500 }); }) );

render(&#x3C;UserList />);

await waitFor(() => {
  expect(screen.getByText('Error loading users')).toBeInTheDocument();
});

});

it('handles network error', async () => { server.use( http.get('/api/users', () => { return HttpResponse.error(); }) );

render(&#x3C;UserList />);

await waitFor(() => {
  expect(screen.getByText('Network error')).toBeInTheDocument();
});

}); });

GraphQL Mocking

// mocks/graphql-handlers.ts import { graphql, HttpResponse } from 'msw';

export const graphqlHandlers = [ graphql.query('GetUsers', () => { return HttpResponse.json({ data: { users: [ { id: '1', name: 'John', email: 'john@example.com' }, { id: '2', name: 'Jane', email: 'jane@example.com' }, ], }, }); }),

graphql.mutation('CreateUser', ({ variables }) => { return HttpResponse.json({ data: { createUser: { id: '3', name: variables.input.name, email: variables.input.email, }, }, }); }),

graphql.query('GetUser', ({ variables }) => { if (variables.id === 'not-found') { return HttpResponse.json({ data: { user: null }, errors: [{ message: 'User not found' }], }); }

return HttpResponse.json({
  data: {
    user: { id: variables.id, name: 'John', email: 'john@example.com' },
  },
});

}), ];

Json-Server

Configuration

// db.json { "users": [ { "id": "1", "name": "John Doe", "email": "john@example.com" }, { "id": "2", "name": "Jane Smith", "email": "jane@example.com" } ], "posts": [ { "id": "1", "title": "Hello World", "authorId": "1" }, { "id": "2", "title": "Another Post", "authorId": "2" } ], "comments": [ { "id": "1", "text": "Great post!", "postId": "1" } ] }

// json-server.config.js module.exports = { port: 3001, watch: true, delay: 100, routes: 'routes.json', };

// routes.json { "/api/*": "/$1", "/users/:id/posts": "/posts?authorId=:id" }

Custom Routes

// server.js const jsonServer = require('json-server'); const server = jsonServer.create(); const router = jsonServer.router('db.json'); const middlewares = jsonServer.defaults();

// Custom middleware for auth server.use((req, res, next) => { if (req.headers.authorization !== 'Bearer valid-token') { if (req.method !== 'GET') { return res.status(401).json({ error: 'Unauthorized' }); } } next(); });

server.use(middlewares);

// Custom routes server.get('/api/me', (req, res) => { res.json({ id: '1', name: 'Current User', email: 'me@example.com' }); });

server.post('/api/login', (req, res) => { const { email, password } = req.body; if (email === 'test@example.com' && password === 'password') { res.json({ token: 'valid-token', user: { id: '1', email } }); } else { res.status(401).json({ error: 'Invalid credentials' }); } });

server.use('/api', router);

server.listen(3001, () => { console.log('Mock API running on http://localhost:3001'); });

Fixture Factories

// mocks/factories/user.ts import { faker } from '@faker-js/faker';

interface User { id: string; name: string; email: string; role: 'admin' | 'user'; createdAt: string; }

export function createUser(overrides: Partial<User> = {}): User { return { id: faker.string.uuid(), name: faker.person.fullName(), email: faker.internet.email(), role: 'user', createdAt: faker.date.past().toISOString(), ...overrides, }; }

export function createUsers(count: number, overrides: Partial<User> = {}): User[] { return Array.from({ length: count }, () => createUser(overrides)); }

// mocks/factories/index.ts export * from './user'; export * from './post'; export * from './comment';

Using Factories in Tests

// tests/components/UserProfile.test.tsx import { createUser } from '../mocks/factories'; import { server } from '../mocks/server'; import { http, HttpResponse } from 'msw';

describe('UserProfile', () => { it('displays admin badge for admin users', async () => { const adminUser = createUser({ role: 'admin', name: 'Admin User' });

server.use(
  http.get('/api/users/:id', () => {
    return HttpResponse.json(adminUser);
  })
);

render(&#x3C;UserProfile userId={adminUser.id} />);

await waitFor(() => {
  expect(screen.getByText('Admin User')).toBeInTheDocument();
  expect(screen.getByTestId('admin-badge')).toBeInTheDocument();
});

}); });

Scenario-Based Mocking

// mocks/scenarios.ts import { http, HttpResponse, delay } from 'msw'; import { createUser, createUsers } from './factories';

export const scenarios = { default: [],

emptyState: [ http.get('/api/users', () => HttpResponse.json([])), http.get('/api/posts', () => HttpResponse.json([])), ],

loadingState: [ http.get('/api/users', async () => { await delay('infinite'); return HttpResponse.json([]); }), ],

errorState: [ http.get('/api/users', () => { return new HttpResponse(null, { status: 500 }); }), ],

largeDataset: [ http.get('/api/users', () => { return HttpResponse.json(createUsers(1000)); }), ],

slowNetwork: [ http.get('/api/*', async ({ request }) => { await delay(2000); // Continue to default handler return undefined; }), ], };

// Usage in tests describe('UserList scenarios', () => { it('empty state', async () => { server.use(...scenarios.emptyState); // Test empty state });

it('error state', async () => { server.use(...scenarios.errorState); // Test error handling }); });

// Usage in Storybook export const EmptyState: Story = { parameters: { msw: { handlers: scenarios.emptyState, }, }, };

Playwright Integration

// tests/e2e/api-mocking.spec.ts import { test, expect } from '@playwright/test';

test.describe('API Mocking', () => { test('mock successful response', async ({ page }) => { await page.route('/api/users', async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify([ { id: '1', name: 'Mocked User', email: 'mock@example.com' }, ]), }); });

await page.goto('/users');
await expect(page.getByText('Mocked User')).toBeVisible();

});

test('mock error response', async ({ page }) => { await page.route('/api/users', async (route) => { await route.fulfill({ status: 500, contentType: 'application/json', body: JSON.stringify({ error: 'Server Error' }), }); });

await page.goto('/users');
await expect(page.getByText('Error loading users')).toBeVisible();

});

test('delay response', async ({ page }) => { await page.route('/api/users', async (route) => { await new Promise((resolve) => setTimeout(resolve, 3000)); await route.fulfill({ status: 200, body: JSON.stringify([]), }); });

await page.goto('/users');
await expect(page.getByTestId('loading-spinner')).toBeVisible();

}); });

Best Practices

  • Use factories: Generate realistic data

  • Define scenarios: Reusable mock configurations

  • Test edge cases: Errors, empty states, loading

  • Keep handlers simple: One responsibility each

  • Match production API: Same contracts

  • Delay appropriately: Realistic timing

  • Document mocks: API contracts

  • Reset between tests: Clean state

Output Checklist

Every API mock setup should include:

  • MSW/json-server configuration

  • Handler definitions

  • Test setup integration

  • Fixture factories

  • Error scenarios

  • Loading states

  • GraphQL support (if needed)

  • Browser integration

  • E2E test mocking

  • Documentation

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

multi-tenant-safety-checker

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

eslint-prettier-config

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

secrets-scanner

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

redis-patterns

No summary provided by upstream source.

Repository SourceNeeds Review