模拟API服务器Skill mock-server

这个技能用于创建和管理模拟API服务器,支持REST和GraphQL API,模拟各种响应场景如延迟、错误和网络故障,便于软件开发和测试。关键词:模拟API、Mock服务器、开发测试、API模拟、测试工具。

测试 0 次安装 0 次浏览 更新于 3/11/2026

name: mock-server description: 创建和管理模拟API服务器用于开发和测试。

模拟服务器技能

创建和管理模拟API服务器用于开发和测试。

指令

您是模拟API服务器专家。当被调用时:

  1. 创建模拟服务器

    • 从OpenAPI规范生成模拟API端点
    • 创建自定义模拟响应
    • 模拟不同响应场景
    • 模拟REST和GraphQL API
    • 处理各种HTTP方法
  2. 配置行为

    • 设置响应延迟/延迟
    • 模拟错误条件
    • 根据输入返回不同响应
    • 实现状态管理
    • 模拟认证
  3. 高级场景

    • 模拟网络故障
    • 随机错误注入
    • 速率限制模拟
    • 条件响应
    • CORS配置
  4. 集成

    • 代理到真实API
    • 记录和重放请求
    • 生成模拟数据
    • 与测试框架集成

使用示例

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

JSON 服务器(简单模拟)

快速设置

# 安装
npm install -g json-server

# 创建 db.json
cat > db.json << EOF
{
  "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", "userId": 1 },
    { "id": 2, "title": "Mock APIs", "userId": 2 }
  ]
}
EOF

# 启动服务器
json-server --watch db.json --port 3000

可用端点(自动生成)

# GET 所有用户
curl http://localhost:3000/users

# GET 用户通过ID
curl http://localhost:3000/users/1

# POST 新用户
curl -X POST http://localhost:3000/users \
  -H "Content-Type: application/json" \
  -d '{"name": "Bob", "email": "bob@example.com"}'

# PUT 更新用户
curl -X PUT http://localhost:3000/users/1 \
  -H "Content-Type: application/json" \
  -d '{"id": 1, "name": "John Updated", "email": "john.new@example.com"}'

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

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

# 查询参数
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"

自定义路由

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

// 使用自定义路由启动
json-server db.json --routes routes.json

模拟服务工作者 (MSW)

设置

npm install --save-dev msw

REST API 模拟

// 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: 'john@example.com' },
      { id: '2', name: 'Jane Smith', email: 'jane@example.com' }
    ]);
  }),

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

    // 模拟未找到
    if (userId === '999') {
      return new HttpResponse(null, { status: 404 });
    }

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

  // POST 创建用户
  http.post('/api/users', async ({ request }) => {
    const data = await request.json();

    // 模拟验证错误
    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 更新用户
  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 用户
  http.delete('/api/users/:userId', ({ params }) => {
    return new HttpResponse(null, { status: 204 });
  }),

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

  // 模拟随机错误
  http.get('/api/unreliable', () => {
    if (Math.random() > 0.5) {
      return new HttpResponse(null, { status: 500 });
    }
    return HttpResponse.json({ status: 'ok' });
  }),

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

    if (email === 'user@example.com' && 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));
}

浏览器设置

// 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 设置(测试)

// 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)

从OpenAPI规范

# 安装
npm install -g @stoplight/prism-cli

# 从OpenAPI规范启动模拟服务器
prism mock openapi.yaml

# 指定端口
prism mock openapi.yaml --port 4010

# 启用验证
prism mock openapi.yaml --errors

# 基于示例的动态响应
prism mock openapi.yaml --dynamic

OpenAPI示例

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: "john@example.com"

  /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: "john@example.com"
        '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 模拟服务器

自定义实现

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

const app = express();

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

// 随机延迟的中间件
app.use((req, res, next) => {
  const delay = Math.random() * 1000; // 0-1秒
  setTimeout(next, delay);
});

// 认证中间件
app.use('/api/*', (req, res, next) => {
  const token = req.headers.authorization;

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

  next();
});

// 内存数据库
let users = [
  { id: '1', name: 'John Doe', email: 'john@example.com' },
  { id: '2', name: 'Jane Smith', email: 'jane@example.com' }
];

// GET 所有用户
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 用户通过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 创建用户
app.post('/api/users', (req, res) => {
  const { name, email } = req.body;

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

  // 检查重复邮箱
  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 更新用户
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 部分更新
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 用户
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();
});

// 错误模拟端点
app.get('/api/simulate-error', (req, res) => {
  const errorType = req.query.type || 'random';

  if (errorType === 'timeout') {
    // 从不响应(超时)
    return;
  }

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

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

// 健康检查
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 模拟服务器

使用 Apollo Server

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

// 模式
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!
  }
`;

// 模拟
const mocks = {
  User: () => ({
    id: () => Math.random().toString(36).substring(7),
    name: () => 'John Doe',
    email: () => 'john@example.com',
    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}`);
});

自定义解析器

const resolvers = {
  Query: {
    users: () => [
      { id: '1', name: 'John Doe', email: 'john@example.com' },
      { id: '2', name: 'Jane Smith', email: 'jane@example.com' }
    ],
    user: (_, { id }) => {
      const users = [
        { id: '1', name: 'John Doe', email: 'john@example.com' },
        { id: '2', name: 'Jane Smith', email: 'jane@example.com' }
      ];
      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)

Docker 设置

# 在Docker中运行WireMock
docker run -d \
  --name wiremock \
  -p 8080:8080 \
  -v $(pwd)/wiremock:/home/wiremock \
  wiremock/wiremock:latest

# 创建映射
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": "john@example.com" }
    ]
  }
}
EOF

模拟数据生成

使用 Faker.js

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

// 生成模拟用户
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()
  };
}

// 生成多个用户
function generateUsers(count = 10) {
  return Array.from({ length: count }, generateUser);
}

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

高级场景

条件响应

// 根据头部返回不同响应
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'
  });
});

// 根据查询参数
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' }]);
});

有状态模拟

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 });
});

网络错误模拟

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

// 超时模拟
http.get('/api/timeout', async () => {
  await delay(30000); // 30秒超时
  return HttpResponse.json({});
});

最佳实践

模拟服务器设计

  • 镜像生产API结构
  • 使用真实数据
  • 实施适当的错误响应
  • 支持分页
  • 包括速率限制模拟

数据管理

  • 使用数据生成器生成真实模拟数据
  • 跨请求保持数据一致
  • 测试运行之间重置状态
  • 支持CRUD操作
  • 实施适当的关系

响应场景

  • 成功响应 (200, 201, 204)
  • 客户端错误 (400, 401, 403, 404, 409)
  • 服务器错误 (500, 502, 503)
  • 网络错误
  • 超时

开发工作流程

  • 前端开发前启动模拟服务器
  • 为API URL使用环境变量
  • 轻松在模拟和真实API之间切换
  • 记录可用端点
  • 保持模拟数据与API同步

测试

  • 使用模拟进行隔离单元测试
  • 测试错误处理
  • 验证请求验证
  • 测试认证流程
  • 模拟边缘情况

工具比较

工具 最佳用途 复杂度 特性
JSON Server 快速原型 自动-CRUD,简单设置
MSW 浏览器/Node测试 请求拦截,强大
Prism OpenAPI规范 基于规范,验证
WireMock 企业,Java 记录,匹配
自定义 Express 完全控制 完全自定义

笔记

  • 前端开发期间使用模拟服务器
  • 保持模拟响应与API合同同步
  • 测试成功和失败场景
  • 为更好测试实施真实延迟
  • 使用环境变量在模拟/真实API之间切换
  • 记录模拟端点和行为
  • 测试之间重置模拟状态
  • 使用数据生成器生成真实数据
  • 为浏览器测试实施适当CORS
  • 与API版本一起版本化模拟数据