example-petstore.com

示例域名 · 并非真实服务 · 浏览器访问显示此页面 · API 请求返回 410 Gone

指南 · 测试

用 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
MSWJavaScript 和 TypeScript:前端开发和单元测试在浏览器内或 Node.js 测试进程内
json-server用一个 JSON 文件生成可用的 REST API,适合原型本地服务器(Node.js),端口 3000

如需使用 Swagger Petstore 示例 API 本身,请在本地运行官方镜像;参见 在找 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。

来源