2 min read

Contract Testing (Pact)

Contract testing is a technique for testing an integration point by checking each application in isolation to ensure the messages it sends or receives conform to a shared understanding that is documented in a "contract".

1. The Problem with Integration Tests

In a microservices architecture, testing how services communicate is challenging.

  • Mocking: Unit tests with mocks are fast but can drift from reality (the "it works on my machine" problem).
  • End-to-End (E2E): Spinning up all services is slow, brittle, and expensive to maintain.

Contract testing bridges this gap. It verifies that the Consumer (e.g., a Frontend) and the Provider (e.g., a Backend API) can communicate without spinning up the full system.

2. Consumer-Driven Contracts (CDC)

The most common approach is Consumer-Driven Contract testing.

  1. Consumer: Defines the exact request it sends and the response it expects. This expectation is the "Contract".
  2. Provider: Verifies that it can fulfill that contract.

3. How Pact Works

Pact is the de-facto standard tool for contract testing.

Step 1: The Consumer Side

The consumer writes a unit test using the Pact SDK. Instead of hitting the real API, it hits a Mock Provider spun up by Pact.

  • The consumer defines: "When I send GET /users/1, I expect a 200 OK with body { "id": 1, "name": "Alice" }".
  • If the test passes, Pact generates a Contract File (a JSON file called a "pact").

Step 2: The Pact Broker

The generated pact file is uploaded to a central Pact Broker. This acts as a repository for all contracts between services.

Step 3: The Provider Side

The provider downloads the pact file from the Broker. It then replays the requests defined in the pact against the real running provider service (or a local instance).

  • If the provider returns a response that matches the contract, the verification passes.

4. Benefits

  • Fast Feedback: Tests run at unit test speed.
  • Independence: Teams can deploy independently. If the contract verification passes, you know you haven't broken the consumer.
  • Documentation: The contracts serve as up-to-date documentation of how services interact.

5. Example Scenario

Contract: Consumer needs id and name.

Provider Response: { "id": 1, "name": "Alice", "age": 30 }

  • Result:Pass. The provider returns more data than needed, but the required fields (id, name) are present and correct.

Provider Response: { "id": 1, "fullName": "Alice" }

  • Result:Fail. The consumer expects name, but the provider sent fullName. This would break the consumer.

programming/software-testing-basics programming/microservices-architecture programming/rest-apis