Contract Testing With Pact: The Missing Test Layer Every Microservices Team Needs
Your unit tests pass. Your E2E tests pass. But your services cannot talk to each other in production. The missing layer: contract testing. Pact verifies that API contracts between services stay in sync — without deploying anything.
What Contract Testing Solves
In microservices, each team owns a service. Team A changes their API response format. Team B’s tests still pass because they mock Team A’s API. In production, Team B’s service crashes because the real response changed.
Contract testing catches this before deployment.
Consumer-Driven Contract Testing with Pact
// Consumer side: define what you expect from the provider
import { PactV3 } from '@pact-foundation/pact';
const provider = new PactV3({
consumer: 'OrderService',
provider: 'UserService',
});
test('get user by ID', async () => {
provider
.given('user with ID 1 exists')
.uponReceiving('a request for user 1')
.withRequest({ method: 'GET', path: '/api/users/1' })
.willRespondWith({
status: 200,
body: {
id: 1,
name: like('John Doe'),
email: like('john@example.com'),
},
});
await provider.executeTest(async (mockserver) => {
const response = await fetch(mockserver.url + '/api/users/1');
const user = await response.json();
expect(user.name).toBeDefined();
expect(user.email).toContain('@');
});
});
Where Contract Testing Fits
| Test Type | What It Validates | Speed |
|---|---|---|
| Unit | Individual functions | Milliseconds |
| Contract | API agreements between services | Seconds |
| Integration | Services working together | Minutes |
| E2E | Full user flows | Minutes |
Contract tests run in seconds (no real services needed), catch integration bugs before deployment, and give teams confidence to deploy independently.
