home / skills / secondsky / claude-skills / api-contract-testing

This skill verifies API contracts between services using consumer-driven tests and schema validation to prevent breaking changes.

npx playbooks add skill secondsky/claude-skills --skill api-contract-testing

Review the files below or copy the command above to add this skill to your agents.

Files (4)
SKILL.md
3.8 KB
---
name: api-contract-testing
description: Verifies API contracts between services using consumer-driven contracts, schema validation, and tools like Pact. Use when testing microservices communication, preventing breaking changes, or validating OpenAPI specifications.
---

# API Contract Testing

Verify that APIs honor their contracts between consumers and providers without requiring full integration tests.

## Key Concepts

| Term | Definition |
|------|------------|
| Consumer | Service that calls an API |
| Provider | Service that exposes an API |
| Contract | Agreed request/response format |
| Pact | Consumer-driven contract testing tool |
| Schema | Structure definition (OpenAPI, JSON Schema) |
| Broker | Central repository for contracts |

## Pact Consumer Test (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('[email protected]')
        })
      })
      .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();
```

## OpenAPI Validation (Express)

```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](references/python-json-schema.md)
- **Java REST Assured**: See [references/java-rest-assured.md](references/java-rest-assured.md)
- **Pact Broker CI/CD**: See [references/pact-broker-cicd.md](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

## Tools

- **Pact** - Multi-language consumer-driven contracts
- **Spring Cloud Contract** - JVM ecosystem
- **OpenAPI/Swagger** - Schema-first validation
- **Dredd** - API blueprint testing
- **Spectral** - OpenAPI linting

Overview

This skill verifies API contracts between services using consumer-driven contracts, schema validation, and tools like Pact. It helps catch breaking changes early, validate OpenAPI specifications, and automate provider verification in CI. The implementation focuses on TypeScript examples but highlights multi-language approaches and CI practices.

How this skill works

The skill creates consumer tests that define expected requests and responses using matchers and mock servers (e.g., Pact). Providers are verified by replaying published contracts against running services, using state handlers to prepare test data. It also integrates schema validation middleware (OpenAPI) to validate requests and responses at runtime and supports publishing and retrieving contracts from a pact broker for CI/CD.

When to use it

  • When developing microservices that communicate via HTTP and you need to prevent breaking changes.
  • Before deploying a provider service to ensure it still satisfies consumer expectations.
  • When validating OpenAPI specs against actual requests and responses in development or staging.
  • To automate contract verification in CI/CD pipelines and record provider compatibility history.
  • When multiple teams or languages are involved and a central contract broker is required.

Best practices

  • Write tests from the consumer perspective and publish pacts to a broker for provider verification.
  • Use flexible matchers (like Pact matchers) to validate structure rather than brittle exact values.
  • Include error and edge-case scenarios in contracts, not just happy paths.
  • Version contracts explicitly and run verification in CI before deployment.
  • Keep contract tests focused on API surface and avoid embedding business logic checks.

Example use cases

  • Consumer test: mock UserService responses in OrderService tests using Pact and MatchersV3.
  • Provider verification: run a Verifier against a deployed provider with state handlers to seed database state.
  • OpenAPI middleware: enforce request/response shapes in Express with express-openapi-validator.
  • CI integration: publish verification results to a pact broker and gate deployments on successful verification.
  • Polyglot environments: combine TypeScript consumers with Java or Python providers using the same contract broker.

FAQ

Do contracts replace integration tests?

No. Contracts complement integration tests by validating agreed API shapes and catching compatibility issues earlier. End-to-end tests are still useful for full-system behavior.

How do I handle breaking changes?

Version the contract, communicate changes to consumers, and run backward-compatibility checks. Use the broker to manage versions and CI gates to prevent unintended breaks.