تخطَّ إلى المحتوى

Pact Cheat Sheet

Overview

Pact is a code-first consumer-driven contract testing framework that ensures service compatibility without requiring integration tests against live services. The consumer defines the expected interactions (the contract or “pact”), and the provider verifies that it can fulfill those expectations. This enables teams to independently deploy microservices with confidence.

Pact supports HTTP API testing and message-based interactions across multiple languages including JavaScript, Java, Python, Go, Ruby, .NET, and Rust. The Pact Broker (or PactFlow SaaS) stores and manages contracts, tracks verification results, and provides a “can-i-deploy” check for CI/CD pipelines to prevent incompatible deployments.

Installation

# JavaScript / Node.js
npm install -D @pact-foundation/pact

# Python
pip install pact-python

# Go
go get github.com/pact-foundation/pact-go/v2

# Ruby
gem install pact

# .NET
dotnet add package PactNet

# Pact CLI tools
npm install -g @pact-foundation/pact-cli

# Pact Broker (Docker)
docker pull pactfoundation/pact-broker

Consumer Side (JavaScript)

const { PactV3 } = require("@pact-foundation/pact");
const { describe, it } = require("mocha");
const { expect } = require("chai");
const axios = require("axios");

const provider = new PactV3({
  consumer: "OrderService",
  provider: "UserService",
  dir: "./pacts",
});

describe("User API", () => {
  it("should return user by ID", async () => {
    // Arrange: define the expected interaction
    provider
      .given("a user with ID 123 exists")
      .uponReceiving("a request for user 123")
      .withRequest({
        method: "GET",
        path: "/api/users/123",
        headers: { Accept: "application/json" },
      })
      .willRespondWith({
        status: 200,
        headers: { "Content-Type": "application/json" },
        body: {
          id: 123,
          name: "John Doe",
          email: "john@example.com",
        },
      });

    // Act & Assert: run the test against the mock
    await provider.executeTest(async (mockserver) => {
      const response = await axios.get(`${mockserver.url}/api/users/123`, {
        headers: { Accept: "application/json" },
      });
      expect(response.data.name).to.equal("John Doe");
      expect(response.data.email).to.equal("john@example.com");
    });
  });
});

Matchers

const { MatchersV3 } = require("@pact-foundation/pact");
const {
  like,
  eachLike,
  regex,
  integer,
  decimal,
  boolean,
  string,
  timestamp,
  uuid,
} = MatchersV3;

// Flexible matching
provider
  .uponReceiving("a list of users")
  .withRequest({ method: "GET", path: "/api/users" })
  .willRespondWith({
    status: 200,
    body: {
      users: eachLike({
        id: integer(1),
        name: string("John Doe"),
        email: regex("john@example.com", "^[\\w.]+@[\\w.]+$"),
        active: boolean(true),
        balance: decimal(99.99),
        createdAt: timestamp("2024-01-15T10:30:00Z", "yyyy-MM-dd'T'HH:mm:ss'Z'"),
        uuid: uuid("12345678-1234-1234-1234-123456789012"),
      }),
      total: integer(100),
    },
  });

Provider Side Verification

const { Verifier } = require("@pact-foundation/pact");

describe("Provider Verification", () => {
  it("should validate the expectations of OrderService", async () => {
    const opts = {
      provider: "UserService",
      providerBaseUrl: "http://localhost:3000",

      // From Pact Broker
      pactBrokerUrl: "https://your-broker.pactflow.io",
      pactBrokerToken: process.env.PACT_BROKER_TOKEN,
      publishVerificationResult: true,
      providerVersion: process.env.GIT_COMMIT,
      providerVersionBranch: process.env.GIT_BRANCH,

      // Or from local files
      // pactUrls: ['./pacts/orderservice-userservice.json'],

      // State handlers
      stateHandlers: {
        "a user with ID 123 exists": async () => {
          await seedDatabase({ id: 123, name: "John Doe" });
        },
        "no users exist": async () => {
          await clearDatabase();
        },
      },
    };

    await new Verifier(opts).verifyProvider();
  });
});

Pact Broker

# Run Pact Broker with Docker Compose
# docker-compose.yml
# services:
#   pact-broker:
#     image: pactfoundation/pact-broker
#     ports:
#       - "9292:9292"
#     environment:
#       PACT_BROKER_DATABASE_URL: "sqlite:///pact_broker.sqlite3"

# Publish pacts to broker
npx pact-broker publish ./pacts \
  --broker-base-url https://your-broker.pactflow.io \
  --broker-token $PACT_BROKER_TOKEN \
  --consumer-app-version $(git rev-parse HEAD) \
  --branch $(git branch --show-current) \
  --tag-with-git-branch

# Can I deploy check
npx pact-broker can-i-deploy \
  --pacticipant OrderService \
  --version $(git rev-parse HEAD) \
  --to-environment production \
  --broker-base-url https://your-broker.pactflow.io \
  --broker-token $PACT_BROKER_TOKEN

# Record deployment
npx pact-broker record-deployment \
  --pacticipant OrderService \
  --version $(git rev-parse HEAD) \
  --environment production

Pact CLI Commands

CommandDescription
pact-broker publishPublish pact files to the broker
pact-broker can-i-deployCheck if a version is safe to deploy
pact-broker record-deploymentRecord a deployment to an environment
pact-broker record-releaseRecord a release to an environment
pact-broker create-environmentCreate a new environment
pact-broker list-latest-pact-versionsList latest pact versions
pact-broker create-or-update-webhookConfigure webhooks

Message-Based Pacts

// Consumer side - message handler
const { MessageConsumerPact } = require("@pact-foundation/pact");

const messagePact = new MessageConsumerPact({
  consumer: "NotificationService",
  provider: "OrderService",
  dir: "./pacts",
});

describe("Order events", () => {
  it("should handle order created event", () => {
    return messagePact
      .given("an order is created")
      .expectsToReceive("an order created event")
      .withContent({
        orderId: like("order-123"),
        userId: like("user-456"),
        total: like(99.99),
        items: eachLike({ productId: like("prod-1"), quantity: integer(1) }),
      })
      .verify(async (message) => {
        const handler = new OrderEventHandler();
        await handler.handle(JSON.parse(message));
        // Assert side effects
      });
  });
});

Advanced Usage

Provider States with Parameters

// Consumer
provider
  .given("a user exists", { userId: "123", name: "John" })
  .uponReceiving("a request for user details")
  .withRequest({ method: "GET", path: "/api/users/123" })
  .willRespondWith({ status: 200 });

// Provider state handler
stateHandlers: {
  "a user exists": async (params) => {
    await createUser({ id: params.userId, name: params.name });
  },
}

Pending Pacts and WIP

const opts = {
  provider: "UserService",
  providerBaseUrl: "http://localhost:3000",
  pactBrokerUrl: "https://your-broker.pactflow.io",
  enablePending: true,
  includeWipPactsSince: "2024-01-01",
  consumerVersionSelectors: [
    { mainBranch: true },
    { deployedOrReleased: true },
  ],
};

Webhook Configuration

# Trigger provider verification when pact changes
npx pact-broker create-or-update-webhook \
  https://ci.example.com/trigger-verify \
  --broker-base-url https://your-broker.pactflow.io \
  --description "Trigger UserService verification" \
  --contract-content-changed \
  --provider UserService

Configuration

# Environment variables
export PACT_BROKER_BASE_URL="https://your-broker.pactflow.io"
export PACT_BROKER_TOKEN="your-read-write-token"
export PACT_DO_NOT_TRACK=true

# .pactrc (Ruby)
# --pact-broker-base-url https://your-broker.pactflow.io
# --broker-token your-token

Troubleshooting

IssueSolution
Pact verification failsCompare consumer expectations with actual provider responses
State handler not foundEnsure provider state name matches exactly between consumer and provider
Broker publish failsCheck authentication token and broker URL
can-i-deploy returns falseCheck which consumer/provider pair has unverified pacts
Matcher mismatchUse like() for structure matching, regex() for format matching
Message pact not generatedEnsure .verify() callback processes the message
Pending pacts ignoredSet enablePending: true in provider verification
Flaky provider testsEnsure state handlers fully set up and tear down test data