api-contract-testing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

API Contract Testing

API契约测试

Verify that APIs honor their contracts between consumers and providers without requiring full integration tests.
无需完整集成测试,即可验证API是否遵守消费者与提供者之间的契约。

Key Concepts

核心概念

TermDefinition
ConsumerService that calls an API
ProviderService that exposes an API
ContractAgreed request/response format
PactConsumer-driven contract testing tool
SchemaStructure definition (OpenAPI, JSON Schema)
BrokerCentral repository for contracts
术语定义
消费者调用API的服务
提供者对外暴露API的服务
契约约定好的请求/响应格式
Pact消费者驱动的契约测试工具
Schema结构定义(OpenAPI、JSON Schema)
契约仓库契约的中央存储库

Pact Consumer Test (TypeScript)

Pact消费者测试(TypeScript)

typescript
import { PactV3, MatchersV3 } from '@pact-foundation/pact';

const provider = new PactV3({
  consumer: 'OrderService',
  provider: 'UserService'
});

describe('User API Contract', () => {
  it('returns user by ID', async () => {
    await provider
      .given('user 123 exists')
      .uponReceiving('a request for user 123')
      .withRequest({ method: 'GET', path: '/users/123' })
      .willRespondWith({
        status: 200,
        body: MatchersV3.like({
          id: '123',
          name: MatchersV3.string('John'),
          email: MatchersV3.email('john@example.com')
        })
      })
      .executeTest(async (mockServer) => {
        const response = await fetch(`${mockServer.url}/users/123`);
        expect(response.status).toBe(200);
      });
  });

  it('returns 404 for non-existent user', async () => {
    await provider
      .given('user does not exist')
      .uponReceiving('a request for non-existent user')
      .withRequest({ method: 'GET', path: '/users/999' })
      .willRespondWith({
        status: 404,
        body: MatchersV3.like({
          error: { code: 'NOT_FOUND', message: MatchersV3.string() }
        })
      })
      .executeTest(async (mockServer) => {
        const response = await fetch(`${mockServer.url}/users/999`);
        expect(response.status).toBe(404);
      });
  });
});
typescript
import { PactV3, MatchersV3 } from '@pact-foundation/pact';

const provider = new PactV3({
  consumer: 'OrderService',
  provider: 'UserService'
});

describe('User API Contract', () => {
  it('returns user by ID', async () => {
    await provider
      .given('user 123 exists')
      .uponReceiving('a request for user 123')
      .withRequest({ method: 'GET', path: '/users/123' })
      .willRespondWith({
        status: 200,
        body: MatchersV3.like({
          id: '123',
          name: MatchersV3.string('John'),
          email: MatchersV3.email('john@example.com')
        })
      })
      .executeTest(async (mockServer) => {
        const response = await fetch(`${mockServer.url}/users/123`);
        expect(response.status).toBe(200);
      });
  });

  it('returns 404 for non-existent user', async () => {
    await provider
      .given('user does not exist')
      .uponReceiving('a request for non-existent user')
      .withRequest({ method: 'GET', path: '/users/999' })
      .willRespondWith({
        status: 404,
        body: MatchersV3.like({
          error: { code: 'NOT_FOUND', message: MatchersV3.string() }
        })
      })
      .executeTest(async (mockServer) => {
        const response = await fetch(`${mockServer.url}/users/999`);
        expect(response.status).toBe(404);
      });
  });
});

Provider Verification

提供者验证

typescript
import { Verifier } from '@pact-foundation/pact';

new Verifier({
  provider: 'UserService',
  providerBaseUrl: 'http://localhost:3000',
  pactBrokerUrl: process.env.PACT_BROKER_URL,
  publishVerificationResult: true,
  providerVersion: process.env.GIT_SHA,
  stateHandlers: {
    'user 123 exists': async () => {
      await db.users.create({ id: '123', name: 'John' });
    },
    'user does not exist': async () => {
      await db.users.deleteAll();
    }
  }
}).verifyProvider();
typescript
import { Verifier } from '@pact-foundation/pact';

new Verifier({
  provider: 'UserService',
  providerBaseUrl: 'http://localhost:3000',
  pactBrokerUrl: process.env.PACT_BROKER_URL,
  publishVerificationResult: true,
  providerVersion: process.env.GIT_SHA,
  stateHandlers: {
    'user 123 exists': async () => {
      await db.users.create({ id: '123', name: 'John' });
    },
    'user does not exist': async () => {
      await db.users.deleteAll();
    }
  }
}).verifyProvider();

OpenAPI Validation (Express)

OpenAPI验证(Express)

javascript
const OpenApiValidator = require('express-openapi-validator');

app.use(OpenApiValidator.middleware({
  apiSpec: './openapi.yaml',
  validateRequests: true,
  validateResponses: true,
  validateSecurity: true
}));
javascript
const OpenApiValidator = require('express-openapi-validator');

app.use(OpenApiValidator.middleware({
  apiSpec: './openapi.yaml',
  validateRequests: true,
  validateResponses: true,
  validateSecurity: true
}));

Additional Implementations

其他实现方式

  • Python JSON Schema: See references/python-json-schema.md
  • Java REST Assured: See references/java-rest-assured.md
  • Pact Broker CI/CD: See references/pact-broker-cicd.md
  • Python JSON Schema:详见 references/python-json-schema.md
  • Java REST Assured:详见 references/java-rest-assured.md
  • Pact Broker CI/CD:详见 references/pact-broker-cicd.md

Best Practices

最佳实践

Do:
  • Test from consumer perspective
  • Use matchers for flexible matching
  • Validate structure, not specific values
  • Version contracts explicitly
  • Test error responses
  • Run tests in CI pipeline
  • Test backward compatibility
Don't:
  • Test business logic in contracts
  • Hard-code specific values
  • Skip error scenarios
  • Ignore versioning
  • Deploy without verification
建议:
  • 从消费者视角进行测试
  • 使用匹配器实现灵活匹配
  • 验证结构而非具体值
  • 明确对契约进行版本控制
  • 测试错误响应
  • 在CI流水线中运行测试
  • 测试向后兼容性
不建议:
  • 在契约测试中验证业务逻辑
  • 硬编码具体值
  • 跳过错误场景
  • 忽略版本控制
  • 未验证就部署

Tools

工具

  • Pact - Multi-language consumer-driven contracts
  • Spring Cloud Contract - JVM ecosystem
  • OpenAPI/Swagger - Schema-first validation
  • Dredd - API blueprint testing
  • Spectral - OpenAPI linting
  • Pact - 多语言消费者驱动契约工具
  • Spring Cloud Contract - JVM生态系统契约工具
  • OpenAPI/Swagger - 基于Schema的优先验证工具
  • Dredd - API蓝图测试工具
  • Spectral - OpenAPI代码检查工具