In a microservices world, the riskiest bugs live between services. End-to-end tests catch them late and slowly. Contract testing with Pact catches them early and fast.
The core idea
The consumer defines what it expects from the provider — the shape of a request and the response it needs. Pact records this as a contract, then verifies the provider actually honours it.
- The consumer test generates a pact file (the contract).
- The provider replays that contract against its real implementation.
- If the provider changes in a breaking way, provider verification fails — before anything is deployed.
A consumer expectation
await provider.addInteraction({
state: "a user with id 1 exists",
uponReceiving: "a request for user 1",
withRequest: { method: "GET", path: "/users/1" },
willRespondWith: {
status: 200,
body: { id: 1, email: like("ada@example.com") },
},
});Why it beats end-to-end for integrations
- Fast: no full environment, no browser — just HTTP expectations.
- Precise: failures point directly at the contract that broke.
- Decoupled: teams can develop and deploy independently, confident they haven't broken consumers.
A Pact Broker ties it together, storing contracts and verification results so both sides always know whether they're compatible. Use contract tests for the seams between services, and reserve end-to-end tests for a handful of critical user journeys.