API 契约测试生成(Pact/Schemathesis/OpenAPI 验证)
从 OpenAPI 规范生成契约测试,验证 Provider 符合契约,支持 Consumer-Driven Contract Testing
提示词
你是契约测试专家。任务:生成 API 契约测试套件。
约束:
- 基于 OpenAPI 3.x 规范
- 生成两类测试:
- Provider 测试:验证实现符合规范(Schemathesis / Dredd / 自定义)
- Consumer 测试:Pact 契约生成与验证
- 覆盖:状态码、响应 Schema、必填字段、枚举值、分页、错误格式
- 数据生成:从 Schema 生成有效/无效测试数据
- CI 集成:Provider 变更时自动跑契约测试
- 输出:测试文件 + CI 配置 + Pact broker 设置
输出格式:
// contract/provider.test.ts (Schemathesis 风格)
// contract/consumer.test.ts (Pact)
.github/workflows/contract.yml:
少样本示例
展示 Schemathesis Provider 验证、Pact Consumer 契约、Schema 匹配器
输入
OpenAPI 片段:
paths:
/users:
get:
summary: List users
parameters:
- $ref: '#/components/parameters/PageParam'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/UserList'
components:
schemas:
UserList:
type: object
required: [data, nextCursor]
properties:
data:
type: array
items: { $ref: '#/components/schemas/User' }
nextCursor:
type: string
nullable: true
输出
// contract/provider.test.ts
import { describe, it, expect } from 'vitest';
import { loadApiSchema, validateResponse } from 'schemathesis';
const api = await loadApiSchema('openapi.yaml');
describe('User API Contract', () => {
it('GET /users returns valid UserList', async () => {
const res = await fetch('/api/users?limit=20');
expect(res.status).toBe(200);
const validation = validateResponse(api, '/users', 'get', res);
expect(validation.errors).toHaveLength(0);
});
it('GET /users validates data array items', async () => {
const res = await fetch('/api/users?limit=5');
const data = await res.json();
expect(Array.isArray(data.data)).toBe(true);
for (const user of data.data) {
expect(user).toHaveProperty('id');
expect(user).toHaveProperty('email');
expect(typeof user.id).toBe('string');
}
});
});
// contract/consumer.test.ts (Pact)
import { PactV3, MatchersV3 } from '@pact-foundation/pact';
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
const { like, eachLike } = MatchersV3;
const provider = new PactV3({ consumer: 'web-app', provider: 'user-api' });
describe('User API Consumer Contract', () => {
beforeAll(() => provider.setup());
afterAll(() => provider.finalize());
it('returns user list', async () => {
provider
.uponReceiving('a request for users')
.withRequest({ method: 'GET', path: '/users', query: { limit: '20' } })
.willRespondWith({
status: 200,
headers: { 'Content-Type': 'application/json' },
body: like({
data: eachLike({ id: like('uuid'), email: like('user@example.com') }),
nextCursor: like('abc123')
})
});
await provider.executeTest(async (mockserver) => {
const res = await fetch(`${mockserver.url}/users?limit=20`);
expect(res.ok).toBe(true);
});
});
});