API · SDK
配置 API 客户端和 SDK
将基础 URL 移出代码,指向真实服务,并添加一项检查,防止示例地址进入生产环境。
指南 · 测试
指向 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 吗?
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 根据您编写或录制的存根文件进行响应。它适用于任何语言,还能模拟真实服务很少能按需产生的慢速响应和故障。
# 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,测试可以添加存根,并检查收到了哪些请求。
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 可将一个 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。
将基础 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。