Skip to content

Contract Testing with Pact — Complete Guide

DodaTech Updated 2026-06-28 3 min read

In this tutorial, you will learn about Contract Testing with Pact. We cover key concepts, practical examples, and best practices to help you master this topic.

Contract testing verifies that API providers and consumers agree on the interface. Pact implements consumer-driven contract testing where consumers define expected interactions and providers verify they can fulfill them.

What You'll Learn

  • Consumer-driven contract testing concepts
  • Writing Pact tests for consumers
  • Provider verification against pacts
  • Pact flow and pact file lifecycle
  • CI/CD integration with Pact Broker

Why It Matters

Contract testing catches breaking changes before deployment. It ensures that API changes do not break consumers. It is faster than end-to-end tests and more reliable than unit tests for integration points.

Real-World Use

Spotify uses contract testing for microservice compatibility. Atlassian uses Pact for internal API contracts. Many financial services use Pact to verify API agreements between teams.

flowchart LR
    Consumer[Consumer Team] --> Test[Consumer Test]
    Test --> Pact[Pact File Generated]
    Pact --> Broker[Pact Broker]
    Broker --> Provider[Provider Team]
    Provider --> Verify[Provider Verification]
    Verify --> Result[Verification Result]
    Result --> Broker
    Broker --> Consumer

Teacher Mindset

Contract tests are not full integration tests. They verify that the consumer's expectations match the provider's actual behavior. Consumers write tests first. Providers verify against those tests.

Code Examples

// Example 1: Consumer Pact test (Node.js)
const { PactV3, MatchersV3 } = require('@pact-foundation/pact');
const { eachLike, like } = MatchersV3;

const provider = new PactV3({
  consumer: 'UserServiceClient',
  provider: 'UserService',
  port: 1234,
});

describe('User API contract', () => {
  it('returns a user by ID', async () => {
    await provider.addInteraction({
      states: [{ description: 'user with ID 1 exists' }],
      uponReceiving: 'a request for user 1',
      withRequest: {
        method: 'GET',
        path: '/api/users/1',
        headers: { Accept: 'application/json' },
      },
      willRespondWith: {
        status: 200,
        headers: { 'Content-Type': 'application/json' },
        body: {
          id: like(1),
          name: like('Alice'),
          email: like('alice@test.com'),
        },
      },
    });

    await provider.executeTest(async (mockServer) => {
      const client = new UserClient(mockServer.url);
      const user = await client.getUser(1);
      expect(user.id).toBe(1);
      expect(user.name).toBe('Alice');
    });
  });
});
# Example 2: Provider verification (Ruby)
require 'pact/provider/rspec'

Pact.service_provider 'UserService' do
  honours_pact_with 'UserServiceClient' do
    pact_uri 'http://pact-broker/pacts/provider/UserService/consumer/UserServiceClient/latest'
  end
end

Pact.provider_states_for 'UserServiceClient' do
  provider_state 'user with ID 1 exists' do
    set_up do
      User.create(id: 1, name: 'Alice', email: 'alice@test.com')
    end
  end
end
# Example 3: Pact Broker CI integration
# docker-compose.yml for Pact Broker
version: '3'
services:
  pact-broker:
    image: pactfoundation/pact-broker
    ports:
      - "9292:9292"
    environment:
      PACT_BROKER_DATABASE_URL: postgres://postgres:password@postgres/pact
  postgres:
    image: postgres
    environment:
      POSTGRES_PASSWORD: password

Common Mistakes

  • Writing contract tests that cover too many scenarios (keep focused on consumer needs)
  • Not running provider verification in CI
  • Allowing contract tests to become full integration tests with database setup
  • Ignoring pact verification failures in deployment pipelines
  • Not versioning pact files or tagging them for different environments

Practice

  1. Write a consumer Pact test for a GET endpoint.
  2. Write the provider verification for the same interaction.
  3. Publish the pact file to a Pact Broker.
  4. Run provider verification in a CI pipeline.
  5. Challenge: Set up a Pact workflow with multiple consumers and a shared provider.

FAQ

What is the difference between contract testing and integration testing?

Contract testing verifies API shape and behavior. Integration testing verifies end-to-end functionality with real dependencies.

Do I need a Pact Broker?

A Pact Broker is recommended for sharing pacts between teams. It stores pact files and verification results.

What languages does Pact support?

Pact supports JVM, JavaScript, Ruby, Go, Python, .NET, Swift, and more.

How do Pact states work?

Provider states set up the necessary data before verification. Each consumer interaction declares the state it needs.

Can I use Pact with asynchronous APIs?

Yes. Pact supports message pacts for asynchronous communication via message queues.

Mini Project

Set up consumer-driven contract testing for a microservice pair. Create a consumer Pact test for 3 endpoints. Set up provider verification with states. Publish pacts to a broker and run verification in CI.

What's Next

Next, you will learn about mock servers for API testing with WireMock and Pretender.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro