QA
Senior SDETAPI TestingFree question

What is consumer-driven contract testing? How does Pact work?

60-second answer

Contract testing checks that two services agree on the shape of their interactions, without deploying them together. In consumer-driven contracts, the consumer writes tests describing the requests it makes and the responses it needs; Pact runs those against a mock provider and records them into a contract file, the pact. The pact is published to a Pact Broker. The provider then verifies the pact against its real implementation in its own pipeline. can-i-deploy checks the broker before release, so a provider change that would break a consumer is caught before it's deployed.

Explanation

Benefits over E2E: fast, isolated, pinpoints the breaking change, scales with many services.

Key concepts:

  • Interaction: given (provider state), upon receiving (request), will respond with (response).
  • Matchers: match types and formats, not exact values, so contracts aren't brittle.
  • Provider states: setup hooks so the provider has the right data.
  • Broker + can-i-deploy + webhooks to trigger provider verification when a pact changes.
  • Contract tests check the interface, not business logic; functional tests are still needed.

Example

The web app (consumer) expects GET /orders/{id} to return status as a string enum. The orders team renames it to orderStatus. Provider verification fails in their PR, before anything reaches staging.

Sample code

import { PactV4, MatchersV3 } from '@pact-foundation/pact';
const { like, regex } = MatchersV3;

const provider = new PactV4({ consumer: 'web-app', provider: 'orders-service' });

it('gets an order', () =>
  provider
    .addInteraction()
    .given('order ORD-1 exists')
    .uponReceiving('a request for order ORD-1')
    .withRequest('GET', '/orders/ORD-1')
    .willRespondWith(200, (b) =>
      b.jsonBody({ id: 'ORD-1', status: regex('PLACED|PAID|SHIPPED', 'PAID'), total: like(999.0) }))
    .executeTest(async (mock) => {
      const order = await new OrdersClient(mock.url).get('ORD-1');
      expect(order.status).toBe('PAID');
    }));