---
title: "用于测试的 Mock API：Prism、WireMock、MSW、json-server"
description: "用本地 Mock API 代替示例地址进行开发和测试：基于 OpenAPI 的 Prism，以及 WireMock、MSW 和 json-server，附示例。"
url: https://example-petstore.com/zh/guides/mock-api
language: zh-Hans
---

# 用 Mock API 测试，而不是示例地址

指向 api.example-petstore.com 这类虚构地址的代码，仍会发出真实的请求，而这些请求会到达该域名的所有者那里。Mock API 能提供同样的便利，却没有这种风险：它运行在您自己的电脑上或测试之中，即时响应，并始终返回您预期的数据。

## 为什么不用示例地址

- 看起来像真实域名的示例地址，可能已被他人注册。这样一来，每个请求，包括带有密钥或令牌的请求头，都会发送到对方的服务器。
- `example.com` 下未使用的名称（例如 `api.example.com`）根本无法解析，因此代码会因超时或 DNS 错误而失败，无法体现它如何处理真实的响应。
- 依赖公共服务器的测试既慢又不稳定，而且一旦该服务器发生变化就会失败。

Mock API 能同时解决这三个问题：它在 `localhost` 上或测试进程内监听，任何数据都不会离开您的电脑。

## 何时使用哪种工具

| 工具 | 最适合 | 运行方式 |
| --- | --- | --- |
| Prism | 已有 OpenAPI 描述，希望响应与之一致 | 本地服务器（Node.js 或 Docker），端口 4010 |
| WireMock | 精确的录制响应、错误场景和延迟，适用于任何语言 | 本地服务器（Java 或 Docker），端口 8080 |
| MSW | JavaScript 和 TypeScript：前端开发和单元测试 | 在浏览器内或 Node.js 测试进程内 |
| json-server | 用一个 JSON 文件生成可用的 REST API，适合原型 | 本地服务器（Node.js），端口 3000 |

如需使用 Swagger Petstore 示例 API 本身，请在本地运行官方镜像；参见 [在找 Swagger Petstore 吗？](https://example-petstore.com/zh/guides/swagger-petstore)

## Prism：基于 OpenAPI 文件

Prism 读取 OpenAPI（或 Swagger 2.0）描述，并用该文件中的示例或模式响应其中的每个操作。与描述不符的请求会收到清晰的验证错误，因此在真实 API 尚未就绪时，也能用它检查客户端。

```
# 使用 Node.js
npm install -g @stoplight/prism-cli
prism mock openapi.yaml

# 使用 Docker
docker run --init --rm -v "$(pwd)":/tmp -p 4010:4010 stoplight/prism:5 mock -h 0.0.0.0 /tmp/openapi.yaml

# 然后
curl http://127.0.0.1:4010/pets
curl http://127.0.0.1:4010/pets/1 -H "Prefer: code=404"
```

`Prefer` 请求头可从描述中选择特定的响应，例如某个错误代码或某个具名示例。使用 `--dynamic`（`-d`）时，Prism 会在每次请求时根据模式生成新数据。

## WireMock：录制的响应

WireMock 根据您编写或录制的存根文件进行响应。它适用于任何语言，还能模拟真实服务很少能按需产生的慢速响应和故障。

```
# mocks/mappings/pet.json
{
  "request":  { "method": "GET", "url": "/v1/pets/1" },
  "response": {
    "status": 200,
    "headers": { "Content-Type": "application/json" },
    "jsonBody": { "id": 1, "name": "Rex", "status": "available" }
  }
}

# 使用包含 mappings/ 的文件夹启动（较大的正文放在 __files/ 中）
docker run -it --rm -p 8080:8080 -v "$(pwd)/mocks":/home/wiremock wiremock/wiremock

curl http://localhost:8080/v1/pets/1
```

在响应中加入 `"fixedDelayMilliseconds": 3000` 可测试超时，加入 `503` 等状态码可测试重试。通过 `/__admin` 上的管理 API，测试可以添加存根，并检查收到了哪些请求。

## MSW 与 PHP 模拟：在测试之中

Mock Service Worker 在应用程序内部拦截请求：在浏览器中通过 Service Worker，在 Node.js 中则在测试进程内。被测代码照常调用其基础 URL，无需运行额外的服务器。

```
// handlers.js
import { http, HttpResponse } from 'msw'

export const handlers = [
  http.get('https://api.example.com/v1/pets/:id', ({ params }) =>
    HttpResponse.json({ id: Number(params.id), name: 'Rex', status: 'available' })),
  http.post('https://api.example.com/v1/pets', () =>
    HttpResponse.json({ error: 'name is required' }, { status: 400 })),
]

// 在测试中（Node.js）
import { setupServer } from 'msw/node'
const server = setupServer(...handlers)
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
```

`onUnhandledRequest: 'error'` 会在代码调用没有处理程序的地址时立即让测试失败，这样遗留的示例地址会在测试运行中暴露出来，而不是出现在生产环境中。在浏览器中，先运行一次 `npx msw init public/`，然后从 `msw/browser` 启动 worker。

在 PHP 测试中，Guzzle 的 `MockHandler` 同样在测试进程内完成这项工作，Symfony 则提供 `MockHttpClient`：

```
// PHP, Guzzle：按顺序响应，无需网络
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;

$mock = new MockHandler([
    new Response(200, ['Content-Type' => 'application/json'], '{"id": 1, "name": "Rex", "status": "available"}'),
    new Response(400, [], '{"error": "name is required"}'),
]);
$client = new Client(['handler' => HandlerStack::create($mock), 'base_uri' => 'https://api.example.com/v1/']);

// PHP, Symfony
$client = new Symfony\Component\HttpClient\MockHttpClient(
    [new Symfony\Component\HttpClient\Response\MockResponse('{"id": 1, "name": "Rex"}')],
    'https://api.example.com/v1/'
);
```

处理程序中的地址是 `api.example.com`：这是一个保留名称，即使有请求绕过了模拟，也永远不会到达真实的服务器。

## json-server：快速搭建 REST API

json-server 可将一个 JSON 文件变成 REST API，提供列表、详情、创建、更新和删除路由。更改会写回该文件，因此很适合原型和演示。

```
# db.json
{
  "pets":   [ { "id": "1", "name": "Rex", "status": "available" } ],
  "orders": []
}

npx json-server db.json

curl http://localhost:3000/pets
curl -X POST http://localhost:3000/orders -H "Content-Type: application/json" -d '{"petId": "1"}'
```

它没有验证和身份认证功能，因此请仅用于原型；契约测试更适合使用 Prism 或 WireMock。

## 切换到真实 API

将基础 URL 放在配置中：开发和测试时以模拟服务为值，只有在应用程序实际运行的地方才使用真实地址：

```
# .env.development
API_BASE_URL=http://localhost:4010

# .env.test
API_BASE_URL=http://localhost:8080

# 生产环境：在托管环境中设置，切勿写入代码仓库
API_BASE_URL=https://api.your-real-service.com
```

更多相关内容，包括阻止示例地址进入生产环境的检查：[配置 API 客户端和 SDK](https://example-petstore.com/zh/guides/api-base-url)。

## 相关指南

### [配置 API 客户端和 SDK](https://example-petstore.com/zh/guides/api-base-url)

将基础 URL 移出代码，指向真实服务，并添加一项检查，防止示例地址进入生产环境。

### [在找 Swagger Petstore 吗？](https://example-petstore.com/zh/guides/swagger-petstore)

Swagger Petstore 示例 API 的真实基础 URL、可用的请求示例、测试密钥，以及如何用 Docker 运行自己的副本。

### [可以放心使用的示例值](https://example-petstore.com/zh/guides/example-values)

保留的域名、IP 地址段、AS 号、MAC 地址、电话号码和测试卡，让示例即使被复制也不会造成危害。

## 来源

- [Prism](https://github.com/stoplightio/prism) Stoplight
- [WireMock Docker images](https://github.com/wiremock/wiremock-docker) WireMock
- [Mock Service Worker](https://mswjs.io/docs/) MSW
- [json-server](https://github.com/typicode/json-server) GitHub
