Back to Skills

mock-server

CuriousLearner
Updated Today
26 views
16
3
16
View on GitHub
Metatestingapi

About

This Claude Skill creates and manages mock API servers for development and testing scenarios. It can generate mock endpoints from OpenAPI specs, simulate various response behaviors like delays and errors, and handle both REST and GraphQL APIs. Developers should use it when they need to test against API dependencies without relying on actual backend services.

Documentation

Mock Server Skill

Create and manage mock API servers for development and testing.

Instructions

You are a mock API server expert. When invoked:

  1. Create Mock Servers:

    • Generate mock API endpoints from OpenAPI specs
    • Create custom mock responses
    • Simulate different response scenarios
    • Mock REST and GraphQL APIs
    • Handle various HTTP methods
  2. Configure Behavior:

    • Set response delays/latency
    • Simulate error conditions
    • Return different responses based on input
    • Implement state management
    • Mock authentication
  3. Advanced Scenarios:

    • Simulate network failures
    • Random error injection
    • Rate limiting simulation
    • Conditional responses
    • CORS configuration
  4. Integration:

    • Proxy to real APIs
    • Record and replay requests
    • Generate mock data
    • Integration with testing frameworks

Usage Examples

@mock-server
@mock-server --from-openapi
@mock-server --port 3000
@mock-server --with-delays
@mock-server --graphql

JSON Server (Simple Mock)

Quick Setup

# Install
npm install -g json-server

# Create db.json
cat > db.json << EOF
{
  "users": [
    { "id": 1, "name": "John Doe", "email": "[email protected]" },
    { "id": 2, "name": "Jane Smith", "email": "[email protected]" }
  ],
  "posts": [
    { "id": 1, "title": "Hello World", "userId": 1 },
    { "id": 2, "title": "Mock APIs", "userId": 2 }
  ]
}
EOF

# Start server
json-server --watch db.json --port 3000

Available Endpoints (Auto-generated)

# GET all users
curl http://localhost:3000/users

# GET user by ID
curl http://localhost:3000/users/1

# POST new user
curl -X POST http://localhost:3000/users \
  -H "Content-Type: application/json" \
  -d '{"name": "Bob", "email": "[email protected]"}'

# PUT update user
curl -X PUT http://localhost:3000/users/1 \
  -H "Content-Type: application/json" \
  -d '{"id": 1, "name": "John Updated", "email": "[email protected]"}'

# PATCH partial update
curl -X PATCH http://localhost:3000/users/1 \
  -H "Content-Type: application/json" \
  -d '{"name": "John Patched"}'

# DELETE user
curl -X DELETE http://localhost:3000/users/1

# Query parameters
curl "http://localhost:3000/users?_page=1&_limit=10"
curl "http://localhost:3000/users?_sort=name&_order=asc"
curl "http://localhost:3000/posts?userId=1"

Custom Routes

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

// Start with custom routes
json-server db.json --routes routes.json

Mock Service Worker (MSW)

Setup

npm install --save-dev msw

REST API Mocking

// src/mocks/handlers.js
import { http, HttpResponse } from 'msw';

export const handlers = [
  // GET users
  http.get('/api/users', () => {
    return HttpResponse.json([
      { id: '1', name: 'John Doe', email: '[email protected]' },
      { id: '2', name: 'Jane Smith', email: '[email protected]' }
    ]);
  }),

  // GET user by ID
  http.get('/api/users/:userId', ({ params }) => {
    const { userId } = params;

    // Simulate not found
    if (userId === '999') {
      return new HttpResponse(null, { status: 404 });
    }

    return HttpResponse.json({
      id: userId,
      name: 'John Doe',
      email: '[email protected]'
    });
  }),

  // POST create user
  http.post('/api/users', async ({ request }) => {
    const data = await request.json();

    // Simulate validation error
    if (!data.email) {
      return HttpResponse.json(
        { error: 'Email is required' },
        { status: 400 }
      );
    }

    return HttpResponse.json(
      {
        id: '123',
        ...data,
        createdAt: new Date().toISOString()
      },
      { status: 201 }
    );
  }),

  // PUT update user
  http.put('/api/users/:userId', async ({ params, request }) => {
    const { userId } = params;
    const data = await request.json();

    return HttpResponse.json({
      id: userId,
      ...data,
      updatedAt: new Date().toISOString()
    });
  }),

  // DELETE user
  http.delete('/api/users/:userId', ({ params }) => {
    return new HttpResponse(null, { status: 204 });
  }),

  // Simulate delay
  http.get('/api/slow-endpoint', async () => {
    await delay(2000); // 2 second delay
    return HttpResponse.json({ message: 'Slow response' });
  }),

  // Simulate random errors
  http.get('/api/unreliable', () => {
    if (Math.random() > 0.5) {
      return new HttpResponse(null, { status: 500 });
    }
    return HttpResponse.json({ status: 'ok' });
  }),

  // Authentication
  http.post('/api/auth/login', async ({ request }) => {
    const { email, password } = await request.json();

    if (email === '[email protected]' && password === 'password') {
      return HttpResponse.json({
        accessToken: 'mock-jwt-token',
        refreshToken: 'mock-refresh-token',
        expiresIn: 3600
      });
    }

    return HttpResponse.json(
      { error: 'Invalid credentials' },
      { status: 401 }
    );
  })
];

function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

Browser Setup

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

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

// src/index.js
if (process.env.NODE_ENV === 'development') {
  const { worker } = await import('./mocks/browser');
  await worker.start();
}

Node.js Setup (Testing)

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

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

// src/tests/setup.js
import { server } from '../mocks/server';

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

Prism (OpenAPI-based)

From OpenAPI Spec

# Install
npm install -g @stoplight/prism-cli

# Start mock server from OpenAPI spec
prism mock openapi.yaml

# Specify port
prism mock openapi.yaml --port 4010

# Enable validation
prism mock openapi.yaml --errors

# Dynamic responses based on examples
prism mock openapi.yaml --dynamic

Example OpenAPI for Prism

openapi: 3.0.0
info:
  title: Mock API
  version: 1.0.0

paths:
  /api/users:
    get:
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'
              examples:
                success:
                  value:
                    - id: "1"
                      name: "John Doe"
                      email: "[email protected]"

  /api/users/{userId}:
    get:
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Success
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
              examples:
                user:
                  value:
                    id: "1"
                    name: "John Doe"
                    email: "[email protected]"
        '404':
          description: Not found
          content:
            application/json:
              examples:
                notFound:
                  value:
                    error: "User not found"

components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        email:
          type: string

Express Mock Server

Custom Implementation

const express = require('express');
const cors = require('cors');

const app = express();

app.use(cors());
app.use(express.json());

// Middleware for random delays
app.use((req, res, next) => {
  const delay = Math.random() * 1000; // 0-1 second
  setTimeout(next, delay);
});

// Middleware for authentication
app.use('/api/*', (req, res, next) => {
  const token = req.headers.authorization;

  if (!token || !token.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Unauthorized' });
  }

  next();
});

// In-memory database
let users = [
  { id: '1', name: 'John Doe', email: '[email protected]' },
  { id: '2', name: 'Jane Smith', email: '[email protected]' }
];

// GET all users
app.get('/api/users', (req, res) => {
  const { page = 1, limit = 10 } = req.query;

  const start = (page - 1) * limit;
  const end = start + parseInt(limit);
  const paginatedUsers = users.slice(start, end);

  res.json({
    data: paginatedUsers,
    meta: {
      page: parseInt(page),
      limit: parseInt(limit),
      total: users.length,
      totalPages: Math.ceil(users.length / limit)
    }
  });
});

// GET user by ID
app.get('/api/users/:id', (req, res) => {
  const user = users.find(u => u.id === req.params.id);

  if (!user) {
    return res.status(404).json({ error: 'User not found' });
  }

  res.json(user);
});

// POST create user
app.post('/api/users', (req, res) => {
  const { name, email } = req.body;

  // Validation
  if (!name || !email) {
    return res.status(400).json({
      error: 'Validation failed',
      details: {
        name: !name ? 'Name is required' : undefined,
        email: !email ? 'Email is required' : undefined
      }
    });
  }

  // Check duplicate email
  if (users.some(u => u.email === email)) {
    return res.status(409).json({ error: 'Email already exists' });
  }

  const newUser = {
    id: String(Date.now()),
    name,
    email,
    createdAt: new Date().toISOString()
  };

  users.push(newUser);
  res.status(201).json(newUser);
});

// PUT update user
app.put('/api/users/:id', (req, res) => {
  const index = users.findIndex(u => u.id === req.params.id);

  if (index === -1) {
    return res.status(404).json({ error: 'User not found' });
  }

  users[index] = {
    ...users[index],
    ...req.body,
    updatedAt: new Date().toISOString()
  };

  res.json(users[index]);
});

// PATCH partial update
app.patch('/api/users/:id', (req, res) => {
  const index = users.findIndex(u => u.id === req.params.id);

  if (index === -1) {
    return res.status(404).json({ error: 'User not found' });
  }

  users[index] = {
    ...users[index],
    ...req.body,
    updatedAt: new Date().toISOString()
  };

  res.json(users[index]);
});

// DELETE user
app.delete('/api/users/:id', (req, res) => {
  const index = users.findIndex(u => u.id === req.params.id);

  if (index === -1) {
    return res.status(404).json({ error: 'User not found' });
  }

  users.splice(index, 1);
  res.status(204).send();
});

// Error simulation endpoint
app.get('/api/simulate-error', (req, res) => {
  const errorType = req.query.type || 'random';

  if (errorType === 'timeout') {
    // Never respond (timeout)
    return;
  }

  if (errorType === 'random' && Math.random() > 0.5) {
    return res.status(500).json({ error: 'Internal server error' });
  }

  res.json({ status: 'ok' });
});

// Health check
app.get('/health', (req, res) => {
  res.json({ status: 'ok', timestamp: new Date().toISOString() });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Mock server running on http://localhost:${PORT}`);
});

GraphQL Mock Server

Using Apollo Server

const { ApolloServer, gql } = require('apollo-server');
const { MockList } = require('@graphql-tools/mock');

// Schema
const typeDefs = gql`
  type User {
    id: ID!
    name: String!
    email: String!
    posts: [Post!]!
  }

  type Post {
    id: ID!
    title: String!
    content: String!
    author: User!
  }

  type Query {
    users: [User!]!
    user(id: ID!): User
    posts: [Post!]!
  }

  type Mutation {
    createUser(name: String!, email: String!): User!
    createPost(title: String!, content: String!, authorId: ID!): Post!
  }
`;

// Mocks
const mocks = {
  User: () => ({
    id: () => Math.random().toString(36).substring(7),
    name: () => 'John Doe',
    email: () => '[email protected]',
    posts: () => new MockList([2, 6])
  }),
  Post: () => ({
    id: () => Math.random().toString(36).substring(7),
    title: () => 'Sample Post',
    content: () => 'This is mock content'
  }),
  Query: () => ({
    users: () => new MockList([10, 20]),
    posts: () => new MockList([20, 40])
  })
};

const server = new ApolloServer({
  typeDefs,
  mocks,
  mockEntireSchema: false
});

server.listen().then(({ url }) => {
  console.log(`GraphQL mock server ready at ${url}`);
});

Custom Resolvers

const resolvers = {
  Query: {
    users: () => [
      { id: '1', name: 'John Doe', email: '[email protected]' },
      { id: '2', name: 'Jane Smith', email: '[email protected]' }
    ],
    user: (_, { id }) => {
      const users = [
        { id: '1', name: 'John Doe', email: '[email protected]' },
        { id: '2', name: 'Jane Smith', email: '[email protected]' }
      ];
      return users.find(u => u.id === id);
    }
  },
  Mutation: {
    createUser: (_, { name, email }) => ({
      id: Math.random().toString(36).substring(7),
      name,
      email
    })
  }
};

const server = new ApolloServer({
  typeDefs,
  resolvers
});

WireMock (Java-based)

Docker Setup

# Run WireMock in Docker
docker run -d \
  --name wiremock \
  -p 8080:8080 \
  -v $(pwd)/wiremock:/home/wiremock \
  wiremock/wiremock:latest

# Create mapping
mkdir -p wiremock/mappings
cat > wiremock/mappings/users.json << EOF
{
  "request": {
    "method": "GET",
    "url": "/api/users"
  },
  "response": {
    "status": 200,
    "headers": {
      "Content-Type": "application/json"
    },
    "jsonBody": [
      { "id": "1", "name": "John Doe", "email": "[email protected]" }
    ]
  }
}
EOF

Mock Data Generation

Using Faker.js

const { faker } = require('@faker-js/faker');

// Generate mock user
function generateUser() {
  return {
    id: faker.string.uuid(),
    name: faker.person.fullName(),
    email: faker.internet.email(),
    avatar: faker.image.avatar(),
    address: {
      street: faker.location.streetAddress(),
      city: faker.location.city(),
      country: faker.location.country()
    },
    createdAt: faker.date.past().toISOString()
  };
}

// Generate multiple users
function generateUsers(count = 10) {
  return Array.from({ length: count }, generateUser);
}

// Generate posts
function generatePost() {
  return {
    id: faker.string.uuid(),
    title: faker.lorem.sentence(),
    content: faker.lorem.paragraphs(3),
    author: generateUser(),
    createdAt: faker.date.past().toISOString()
  };
}

Advanced Scenarios

Conditional Responses

// Return different responses based on headers
http.get('/api/users', ({ request }) => {
  const acceptLanguage = request.headers.get('Accept-Language');

  if (acceptLanguage?.includes('es')) {
    return HttpResponse.json({
      mensaje: 'Hola Mundo'
    });
  }

  return HttpResponse.json({
    message: 'Hello World'
  });
});

// Based on query parameters
http.get('/api/users', ({ request }) => {
  const url = new URL(request.url);
  const format = url.searchParams.get('format');

  if (format === 'xml') {
    return new HttpResponse(
      '<users><user>John</user></users>',
      { headers: { 'Content-Type': 'application/xml' } }
    );
  }

  return HttpResponse.json([{ name: 'John' }]);
});

Stateful Mocking

let requestCount = 0;

http.get('/api/rate-limit', () => {
  requestCount++;

  if (requestCount > 10) {
    return HttpResponse.json(
      { error: 'Rate limit exceeded' },
      { status: 429 }
    );
  }

  return HttpResponse.json({ count: requestCount });
});

Network Error Simulation

http.get('/api/network-error', () => {
  return HttpResponse.error();
});

// Timeout simulation
http.get('/api/timeout', async () => {
  await delay(30000); // 30 second timeout
  return HttpResponse.json({});
});

Best Practices

Mock Server Design

  • Mirror production API structure
  • Use realistic data
  • Implement proper error responses
  • Support pagination
  • Include rate limiting simulation

Data Management

  • Use data generators for realistic mock data
  • Maintain consistent data across requests
  • Reset state between test runs
  • Support CRUD operations
  • Implement proper relationships

Response Scenarios

  • Success responses (200, 201, 204)
  • Client errors (400, 401, 403, 404, 409)
  • Server errors (500, 502, 503)
  • Network errors
  • Timeouts

Development Workflow

  • Start mock server before frontend development
  • Use environment variables for API URLs
  • Switch between mock and real API easily
  • Document available endpoints
  • Keep mock data synchronized with API

Testing

  • Use mocks for isolated unit tests
  • Test error handling
  • Verify request validation
  • Test authentication flows
  • Simulate edge cases

Tools Comparison

ToolBest ForComplexityFeatures
JSON ServerQuick prototypesLowAuto-CRUD, simple setup
MSWBrowser/Node testingMediumRequest interception, powerful
PrismOpenAPI specsLowSpec-based, validation
WireMockEnterprise, JavaHighRecording, matching
Custom ExpressFull controlMediumComplete customization

Notes

  • Use mock servers during frontend development
  • Keep mock responses synchronized with API contracts
  • Test both success and failure scenarios
  • Implement realistic delays for better testing
  • Use environment variables to switch between mock/real APIs
  • Document mock endpoints and behaviors
  • Reset mock state between tests
  • Use data generators for realistic data
  • Implement proper CORS for browser testing
  • Version mock data alongside API versions

Quick Install

/plugin add https://github.com/CuriousLearner/devkit/tree/main/mock-server

Copy and paste this command in Claude Code to install this skill

GitHub 仓库

CuriousLearner/devkit
Path: skills/mock-server

Related Skills