api-contract-testing
0
总安装量
1
周安装量
安装命令
npx skills add https://github.com/pfangueiro/claude-code-agents --skill api-contract-testing
Agent 安装分布
amp
1
cline
1
opencode
1
cursor
1
continue
1
kimi-cli
1
Skill 文档
API Contract Testing Skill
Provides tools and patterns for API contract testing using OpenAPI, JSON Schema, and contract-first development.
Purpose
This skill provides:
- OpenAPI specification validation
- JSON Schema contract enforcement
- API versioning strategies
- Consumer-driven contract testing (PACT)
- Mock server generation
- Contract regression testing
When to Use
- “Validate API against OpenAPI spec”
- “Create API contract tests”
- “Generate mock server from OpenAPI”
- “Test API versioning compatibility”
- “Implement consumer-driven contracts”
OpenAPI Validation
OpenAPI 3.0 Specification Example
openapi: 3.0.0
info:
title: User API
version: 1.0.0
description: User management API
servers:
- url: https://api.example.com/v1
description: Production server
- url: https://staging-api.example.com/v1
description: Staging server
paths:
/users:
get:
summary: List all users
operationId: listUsers
parameters:
- name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 20
responses:
'200':
description: Successful response
content:
application/json:
schema:
type: object
properties:
users:
type: array
items:
$ref: '#/components/schemas/User'
total:
type: integer
post:
summary: Create a new user
operationId: createUser
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UserCreate'
responses:
'201':
description: User created
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'400':
description: Bad request
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/users/{userId}:
get:
summary: Get user by ID
operationId: getUserById
parameters:
- name: userId
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'404':
description: User not found
components:
schemas:
User:
type: object
required:
- id
- email
- name
properties:
id:
type: string
format: uuid
readOnly: true
email:
type: string
format: email
name:
type: string
minLength: 1
maxLength: 100
createdAt:
type: string
format: date-time
readOnly: true
UserCreate:
type: object
required:
- email
- name
properties:
email:
type: string
format: email
name:
type: string
minLength: 1
maxLength: 100
password:
type: string
format: password
minLength: 8
Error:
type: object
required:
- message
properties:
message:
type: string
errors:
type: array
items:
type: object
properties:
field:
type: string
message:
type: string
Contract Testing with Vitest
// tests/api-contract.test.ts
import { describe, it, expect } from 'vitest'
import OpenAPIValidator from 'express-openapi-validator'
import SwaggerParser from '@apidevtools/swagger-parser'
const SPEC_PATH = './openapi.yaml'
describe('API Contract Tests', () => {
it('should have valid OpenAPI specification', async () => {
const api = await SwaggerParser.validate(SPEC_PATH)
expect(api).toBeDefined()
expect(api.openapi).toBe('3.0.0')
})
it('should validate request/response against spec', async () => {
const validator = await OpenAPIValidator.middleware({
apiSpec: SPEC_PATH,
validateRequests: true,
validateResponses: true,
})
expect(validator).toBeDefined()
})
})
Consumer-Driven Contract Testing (PACT)
Provider Test (API Server)
// tests/pact-provider.test.ts
import { Verifier } from '@pact-foundation/pact'
import path from 'path'
describe('Pact Provider Verification', () => {
it('should validate against consumer contracts', async () => {
const opts = {
provider: 'UserAPI',
providerBaseUrl: 'http://localhost:3000',
pactUrls: [
path.resolve(__dirname, '../pacts/consumer-userapi.json'),
],
stateHandlers: {
'user exists': async () => {
// Setup test data
await db.users.create({
id: 'test-user-id',
email: 'test@example.com',
name: 'Test User',
})
},
},
}
await new Verifier(opts).verifyProvider()
})
})
Consumer Test (Frontend)
// tests/pact-consumer.test.ts
import { PactV3, MatchersV3 } from '@pact-foundation/pact'
import { getUserById } from '../api/users'
const { like, iso8601DateTime } = MatchersV3
describe('User API Consumer', () => {
const provider = new PactV3({
consumer: 'WebApp',
provider: 'UserAPI',
})
it('should get user by ID', async () => {
await provider
.given('user exists')
.uponReceiving('a request for user by ID')
.withRequest({
method: 'GET',
path: '/users/test-user-id',
})
.willRespondWith({
status: 200,
headers: { 'Content-Type': 'application/json' },
body: {
id: 'test-user-id',
email: like('test@example.com'),
name: like('Test User'),
createdAt: iso8601DateTime(),
},
})
.executeTest(async (mockServer) => {
const user = await getUserById('test-user-id', mockServer.url)
expect(user.id).toBe('test-user-id')
expect(user.email).toMatch(/^.+@.+\..+$/)
})
})
})
Mock Server Generation
Using Prism (OpenAPI Mock Server)
# Install Prism
npm install -g @stoplight/prism-cli
# Start mock server from OpenAPI spec
prism mock openapi.yaml --port 4010
# Mock server with dynamic examples
prism mock openapi.yaml --dynamic
# Validate requests only (proxy to real API)
prism proxy openapi.yaml https://api.example.com
Postman Collection from OpenAPI
// scripts/generate-postman.ts
import { convert } from 'openapi-to-postmanv2'
import fs from 'fs'
const openapiSpec = JSON.parse(fs.readFileSync('./openapi.json', 'utf8'))
convert(
{ type: 'json', data: openapiSpec },
{},
(err, conversionResult) => {
if (!conversionResult.result) {
console.error('Conversion failed:', conversionResult.reason)
return
}
const collection = conversionResult.output[0].data
fs.writeFileSync(
'./postman-collection.json',
JSON.stringify(collection, null, 2)
)
}
)
API Versioning Strategies
URL Versioning
// v1/routes.ts
export const v1Routes = {
'/users': getUsersV1,
'/users/:id': getUserByIdV1,
}
// v2/routes.ts (breaking change)
export const v2Routes = {
'/users': getUsersV2, // Returns different schema
'/users/:id': getUserByIdV2,
}
// app.ts
app.use('/v1', v1Routes)
app.use('/v2', v2Routes)
Header Versioning
// middleware/version.ts
export function versionMiddleware(req, res, next) {
const version = req.headers['api-version'] || '1.0'
if (version === '1.0') {
req.apiVersion = 'v1'
} else if (version === '2.0') {
req.apiVersion = 'v2'
} else {
return res.status(400).json({ error: 'Unsupported API version' })
}
next()
}
Contract Regression Testing
// tests/contract-regression.test.ts
import { describe, it, expect } from 'vitest'
import SwaggerParser from '@apidevtools/swagger-parser'
describe('API Contract Regression', () => {
it('should not introduce breaking changes', async () => {
const previousSpec = await SwaggerParser.validate('./previous-openapi.yaml')
const currentSpec = await SwaggerParser.validate('./openapi.yaml')
// Check that all previous endpoints still exist
for (const path in previousSpec.paths) {
expect(currentSpec.paths[path]).toBeDefined()
for (const method in previousSpec.paths[path]) {
expect(currentSpec.paths[path][method]).toBeDefined()
// Verify response schemas are compatible
const prevResponses = previousSpec.paths[path][method].responses
const currResponses = currentSpec.paths[path][method].responses
for (const statusCode in prevResponses) {
expect(currResponses[statusCode]).toBeDefined()
}
}
}
})
it('should maintain backward compatibility for required fields', async () => {
const previousSpec = await SwaggerParser.validate('./previous-openapi.yaml')
const currentSpec = await SwaggerParser.validate('./openapi.yaml')
for (const schemaName in previousSpec.components?.schemas) {
const prevSchema = previousSpec.components.schemas[schemaName]
const currSchema = currentSpec.components.schemas[schemaName]
if (prevSchema.required && currSchema?.required) {
// New spec must include all previously required fields
for (const field of prevSchema.required) {
expect(currSchema.required).toContain(field)
}
}
}
})
})
Best Practices
-
Contract-First Development
- Define OpenAPI spec before implementation
- Generate types from spec
- Validate during development
-
Versioning
- Use semantic versioning
- Deprecate endpoints gradually
- Document breaking changes
-
Testing
- Test both provider and consumer sides
- Automate contract validation in CI/CD
- Run regression tests on spec changes
-
Documentation
- Keep OpenAPI spec in sync with code
- Generate interactive API docs
- Provide migration guides for version changes
Integration with Agents
Works best with:
- api-backend agent – Generates OpenAPI specs and validation
- test-automation agent – Creates contract tests
- architecture-planner agent – Designs API versioning strategy
Tools & Libraries
- OpenAPI Validation:
express-openapi-validator,swagger-parser - Contract Testing:
@pact-foundation/pact - Mock Servers:
@stoplight/prism-cli,json-server - Code Generation:
openapi-generator,swagger-codegen