Eliminating Microservice Integration Bottlenecks with Consumer-Driven Contract Testing
Mon Aug 15 2016
In a microservices architecture, testing every permutation of service communication using end-to-end integration tests is an unscalable anti-pattern. End-to-end suites are notoriously slow, require all dependent services to be deployed in a pristine state simultaneously, and fail for reasons unrelated to the code under test.
Consumer-Driven Contract Testing (CDCT) solves this by shifting the responsibility of contract definition to the consumers of an API.
How Consumer-Driven Contracts Work
Instead of the API provider dictating a static OpenAPI schema, each downstream consumer defines an explicit contract specifying the exact request parameters it sends and the minimal subset of JSON attributes it requires in response.
[ Consumer Unit Test ] ── Generates Pact JSON ──> [ Pact Broker Matrix ] │ ▼ [ Provider CI Pipeline ] <── Verifies Schema <─── [ Fetch Active Pacts ]
Writing a Consumer Contract in TypeScript
The consumer writes a test using a mock provider that generates a .json Pact artifact:
import { PactV3, Matchers } from '@pact-foundation/pact';
const provider = new PactV3({
consumer: 'CheckoutService',
provider: 'UserService',
});
describe('User API Contract', () => {
it('returns valid user details for payment processing', async () => {
provider
.given('a user exists with ID 42')
.uponReceiving('a request for user details')
.withRequest({
method: 'GET',
path: '/api/v1/users/42',
})
.willRespondWith({
status: 200,
headers: { 'Content-Type': 'application/json' },
body: {
id: Matchers.like(42),
email: Matchers.email(),
accountStatus: Matchers.regex(/ACTIVE|SUSPENDED/, 'ACTIVE'),
},
});
await provider.executeTest(async (mockServer) => {
const response = await fetch(`${mockServer.url}/api/v1/users/42`);
expect(response.status).toBe(200);
});
});
});
The can-i-deploy Safety Gate
Before any microservice is deployed to production, the CI pipeline executes a simple CLI verification query against the central Pact Broker:
Bash
pact-broker can-i-deploy \
--pacticipant UserService \
--version $GIT_COMMIT_HASH \
--to-environment production
If the proposed provider build modifies or removes a JSON field that any active consumer relies on, can-i-deploy exits with a non-zero status code, immediately stopping the pipeline before broken code hits production.