Testing each service independently, with no shared test environment and no orchestration problem. That's what contract testing delivers in a microservices architecture, and it works differently than most teams think.
At many teams the integration test still runs on the same old recipe: spin up the whole stack, hope everything works together, and wait three hours for a failing pipeline. Contract testing breaks that pattern.
With Pact you're not testing whether everything runs together, but whether each service honours what the others expect from it. In seconds, not minutes.
The principle comes down to three steps.
Service A, the consumer, records which request it sends and which response it expects.
That contract gets published to a central store, the Pact Broker.
Service B, the provider, runs the contracts as tests, without service A having to be active at all.
The result: every service gets tested independently, with no shared test environment and no orchestration problem.
Say the checkout service (consumer) calls the user service (provider) for customer data. Here's what that contract looks like in practice.
// records the request/response checkout-service expects
const { PactV3 } = require('@pact-foundation/pact');
const provider = new PactV3({ consumer: 'checkout-service', provider: 'user-service' });
provider
.given('user 123 exists')
.uponReceiving('a request for user 123')
.withRequest({ method: 'GET', path: '/users/123' })
.willRespondWith({
status: 200,
body: { id: 123, name: 'Jelle', email: 'jelle@qadvice.be' }
});
it('gets the user', () =>
provider.executeTest(async (mockserver) => {
const res = await fetch(`${mockserver.url}/users/123`);
expect(res.status).toBe(200);
})
);
This test automatically generates the contract and publishes it to the Pact Broker:
// abbreviated contract, stored in the Pact Broker
{
"consumer": { "name": "checkout-service" },
"provider": { "name": "user-service" },
"interactions": [{
"description": "a request for user 123",
"request": { "method": "GET", "path": "/users/123" },
"response": {
"status": 200,
"body": { "id": 123, "name": "Jelle", "email": "jelle@qadvice.be" }
}
}]
}
The provider verifies that contract without checkout-service ever having to run:
// replays every published contract against the real provider
const { Verifier } = require('@pact-foundation/pact');
new Verifier({
provider: 'user-service',
providerBaseUrl: 'http://localhost:3001',
pactBrokerUrl: 'https://pact.qadvice.be',
publishVerificationResult: true,
providerVersion: '1.4.2'
}).verifyProvider();
If user-service stops returning the email field, verification fails immediately, and you know exactly which service and which field are the problem, without checkout-service ever having to spin up.
When a contract test fails, it's immediately clear which service no longer delivers which field, and who needs to fix it. The Pact Broker also offers a can-i-deploy check: is it safe to release this version, given every contract linked to it? The result is fewer flaky tests, less distrust in the suite, and a faster release cycle.
Contract testing works well for REST APIs, so GET and POST interfaces between services, and also works for async messaging such as Kafka, RabbitMQ or SNS/SQS. For complex, stateful multi-step interactions it's less suited. And for critical user flows you still need end-to-end testing, contract testing doesn't replace that.
Contracts that don't evolve with the code create a false sense of security. A contract nobody updates anymore isn't testing anything meaningful.
Over-specifying makes the provider unnecessarily fragile. Only test what the consumer actually uses, not every field that happens to sit in the response.
Contract testing validates interfaces, not business logic. Keep a lean end-to-end suite for the paths that genuinely matter.
Contract testing works best as an addition to a well-thought-out test strategy, not as a replacement for it. Less end-to-end, more confidence, faster releases: that's what it delivers when you use it well.
We'll review your test approach and tell you honestly where the gains are. And where they aren't.
Book a call