API

Contract testing: guaranteeing the contract between services

In architectures with many services, the biggest risk is not inside each service; it's in the contract between them. One team changes the shape of a response and, without realizing it, breaks three consumers. Contract testing exists to prevent exactly that.

The problem with E2E tests across services

Spinning up all the services together to test the integration is slow, expensive, and brittle. Contract testing swaps that for a lightweight check: each side proves it honors the agreed contract, without needing the other one to be up.

Consumer-driven contracts

With tools like Pact, the consumer declares what it expects from the API. This generates a contract that the provider verifies in its own pipeline.

// Consumer side: "I expect this format"
await provider.addInteraction({
  state: 'user 1 exists',
  uponReceiving: 'a request for user 1',
  withRequest:  { method: 'GET', path: '/users/1' },
  willRespondWith: {
    status: 200,
    body: { id: 1, name: like('Diogo') }
  }
});

The flow

  1. The consumer defines expectations and generates the pact (the contract).
  2. The contract is published to a broker.
  3. The provider runs the contract against its real API and proves it honors it.
  4. Broke the contract? The provider's pipeline fails, before it ever reaches production.
Contract testing gives you the best of both worlds: the confidence of integration with the speed of a unit test.

Conclusion

For distributed systems, versioned and automatically verified contracts are a powerful safety net. They turn "did I break anyone?" into an objective answer given by the pipeline.

Read also

Back to the blog