Contract Testing
Implements contract testing methods to validate the agreements between application services and external APIs. Ensure both the consumer and provider follow specified contracts like data formats and structures.
When to Use
- When your application relies on third-party services.
- To ensure that changes in API specifications do not break your application.
- Before deployment to avoid runtime issues caused by contract violations.
Core Workflow
- Choose Contract Testing Tool
Select a framework tailored for contract testing (e.g.,Pact,Hoverfly).# For JavaScript npm install @pact-foundation/pact - Define Consumer and Provider Contracts
Define what your service expects from external APIs.const { Pact } = require('@pact-foundation/pact'); const provider = new Pact({ consumer: "YourService", provider: "ExternalAPI" }); provider .uponReceiving('a request for data') .withRequest('GET', '/data') .willRespondWith({ status: 200, body: { message: "Success" } }); - Run Contract Tests
Execute the contract tests and ensure compliance with expectations.npm test - Handle Contract Violations
Repair code or update your contracts as necessary based on your test results.
Implementation Patterns
Pattern 1: Using Pact for Consumer-Driven Contracts
const { Pact } = require('@pact-foundation/pact');
describe('Pact with Our API', () => {
const provider = new Pact({
consumer: 'Consumer',
provider: 'APIProvider',
});
beforeAll(() => provider.setup());
it('it should return a successful response', async () => {
// Arrange
await provider.addInteraction({
state: 'data exists',
uponReceiving: 'a request for data',
withRequest: { method: 'GET', path: '/data' },
willRespondWith: { status: 200, body: { message: 'Success' } },
});
// Act
const response = await fetch('http://localhost:3000/data');
const body = await response.json();
// Assert
expect(body.message).toEqual('Success');
});
afterAll(() => provider.finalize());
});
Implementation Patterns
Pattern 2: Provider Verification with Pact (Python)
On the provider side, use pact-python to verify that the API satisfies the consumer's expectations:
from pact import Verifier
def test_provider_meets_consumer_contract():
"""Verify the provider API satisfies the Pact contract."""
verifier = Verifier(
provider="APIProvider",
provider_base_url="http://localhost:8000",
)
# Load the Pact file published by the consumer
pact_url = "pacts/consumer-apiprovider.json"
# Verify all interactions from the consumer's Pact
success, logs = verifier.verify_pacts(
pact_url,
provider_states_setup_url=f"{verifier.provider_base_url}/_pact/setup",
verbose=False,
)
assert success, f"Provider verification failed: {logs}"
The consumer writes tests and publishes a Pact file. The provider loads that file and verifies every interaction actually works against the real API. This catches breaking changes before deployment.
# Example Pact file structure (generated by consumer tests)
# {
# "consumer": {"name": "Consumer"},
# "provider": {"name": "APIProvider"},
# "interactions": [{
# "description": "a request for data",
# "request": {"method": "GET", "path": "/data"},
# "response": {"status": 200, "body": {"message": "Success"}}
# }]
# }
Constraints
MUST DO
- Regularly update contracts and documentation to reflect changes.
- Ensure that the API service is functional before running contract tests.
MUST NOT DO
- Bypass contract tests; they are essential for integration continuity.
- Assume defaults; always explicitly define contracts.