Contract Test Validator
Overview
Validate API contracts between services using consumer-driven contract testing to prevent breaking changes in microservice architectures. Supports Pact (the industry standard for CDC testing), Spring Cloud Contract (JVM), and OpenAPI-diff for specification comparison.
Prerequisites
-
Contract testing framework installed (Pact JS/Python/JVM, or Spring Cloud Contract)
-
Pact Broker running (or PactFlow SaaS) for contract storage and verification
-
Consumer and provider services with clearly defined API boundaries
-
Existing integration points documented (which consumers call which provider endpoints)
-
CI pipeline configured for both consumer and provider repositories
Instructions
-
Identify consumer-provider relationships in the system:
-
Map which services call which APIs (e.g., Frontend calls User API, Order API calls Payment API).
-
Document each interaction: HTTP method, path, headers, request body, expected response.
-
Prioritize contracts for the most critical and frequently changing integrations.
-
Write consumer-side contract tests (Pact consumer tests):
-
Define the expected interaction: method, path, query parameters, headers, request body.
-
Specify the expected response: status code, headers, and response body structure.
-
Use matchers for flexible assertions (like() , eachLike() , term() ) instead of exact values.
-
Generate a Pact file (JSON contract) from the consumer test.
-
Publish consumer contracts to the Pact Broker:
-
Run pact-broker publish with the consumer version and branch/tag.
-
Enable webhooks to trigger provider verification when new contracts are published.
-
Configure can-i-deploy checks in CI to gate deployments.
-
Write provider-side verification tests:
-
Configure the Pact verifier to fetch contracts from the Pact Broker.
-
Set up provider states (test data scenarios matching consumer expectations).
-
Run verification against the actual provider implementation.
-
Publish verification results back to the Pact Broker.
-
Handle contract evolution:
-
Adding new fields: Safe -- consumers using matchers will not break.
-
Removing fields: Breaking -- coordinate with all consumers before removal.
-
Changing field types: Breaking -- requires consumer updates first.
-
Use can-i-deploy to check compatibility before releasing either side.
-
For schema-based validation (non-Pact):
-
Compare OpenAPI spec versions using openapi-diff to detect breaking changes.
-
Flag removed endpoints, changed parameter types, and narrowed response schemas.
-
Run schema validation tests against the actual API responses.
-
Integrate contract tests into the CI/CD pipeline for both consumers and providers.
Output
-
Consumer Pact test files defining expected API interactions
-
Generated Pact contract files (JSON) in pacts/ directory
-
Provider verification test configuration
-
Pact Broker deployment with published contracts and verification status
-
CI pipeline integration with can-i-deploy deployment gates
-
Contract evolution report flagging breaking vs. non-breaking changes
Error Handling
Error Cause Solution
Provider verification fails Provider response does not match consumer expectations Check if the contract is outdated; update consumer tests if the change is intentional; fix provider if regression
can-i-deploy blocks release Consumer has unverified or failed contracts Run provider verification; check if the right version tags are published; verify Pact Broker webhook fired
Pact Broker connection error Broker URL or credentials misconfigured Verify PACT_BROKER_BASE_URL and PACT_BROKER_TOKEN environment variables; check network connectivity
Provider state not found Consumer test references a state the provider does not implement Add the missing provider state setup function; align state names between consumer and provider
Too many contracts to maintain Every consumer-provider pair has extensive contracts Focus on critical interactions; use matchers instead of exact values; consolidate similar interactions
Examples
Pact consumer test (JavaScript):
import { PactV4 } from '@pact-foundation/pact';
const provider = new PactV4({ consumer: 'Frontend', provider: 'UserAPI' });
describe('User API Contract', () => {
it('fetches a user by ID', async () => {
await provider
.addInteraction()
.given('user with ID 1 exists')
.uponReceiving('a request for user 1')
.withRequest('GET', '/api/users/1', (builder) => {
builder.headers({ Accept: 'application/json' });
})
.willRespondWith(200, (builder) => { # HTTP 200 OK
builder
.headers({ 'Content-Type': 'application/json' })
.jsonBody({
id: like('1'),
name: like('Alice'),
email: like('alice@example.com'),
});
})
.executeTest(async (mockServer) => {
const response = await fetch(${mockServer.url}/api/users/1);
const user = await response.json();
expect(user.name).toBeDefined();
});
});
});
Provider verification test:
import { Verifier } from '@pact-foundation/pact';
describe('User API Provider Verification', () => { it('validates consumer contracts', async () => { await new Verifier({ providerBaseUrl: 'http://localhost:3000', # 3000: 3 seconds in ms pactBrokerUrl: process.env.PACT_BROKER_BASE_URL, pactBrokerToken: process.env.PACT_BROKER_TOKEN, provider: 'UserAPI', publishVerificationResult: true, providerVersion: process.env.GIT_SHA, stateHandlers: { 'user with ID 1 exists': async () => { await db.users.create({ id: '1', name: 'Alice', email: 'alice@example.com' }); }, }, }).verifyProvider(); }); });
can-i-deploy CI check:
pact-broker can-i-deploy
--pacticipant Frontend
--version $(git rev-parse HEAD)
--to-environment production
--broker-base-url $PACT_BROKER_URL
--broker-token $PACT_BROKER_TOKEN
Resources
-
Pact documentation: https://docs.pact.io/
-
PactFlow (managed Pact Broker): https://pactflow.io/
-
Spring Cloud Contract: https://spring.io/projects/spring-cloud-contract
-
openapi-diff: https://github.com/OpenAPITools/openapi-diff
-
Consumer-Driven Contracts: https://martinfowler.com/articles/consumerDrivenContracts.html