0
0
MicroservicesHow-ToBeginner ยท 4 min read

How to Use Pact for Contract Testing in Microservices

Use Pact to create consumer-driven contract tests by defining expected interactions in the consumer tests and verifying them against the provider. This ensures both sides agree on the API contract before integration, preventing breaking changes.
๐Ÿ“

Syntax

The basic syntax for using Pact involves creating a consumer test that defines expected requests and responses, then running a provider verification to check the provider against the contract.

Key parts include:

  • pact: Defines the contract between consumer and provider.
  • given: Sets provider state before interaction.
  • uponReceiving: Describes the request the consumer will make.
  • withRequest: Details HTTP method, path, headers, and body.
  • willRespondWith: Defines expected response status, headers, and body.
javascript
const { Pact } = require('@pact-foundation/pact');

const provider = new Pact({
  consumer: 'ConsumerService',
  provider: 'ProviderService',
  port: 1234
});

provider
  .setup()
  .then(() => {
    return provider.addInteraction({
      state: 'provider has data',
      uponReceiving: 'a request for data',
      withRequest: {
        method: 'GET',
        path: '/data'
      },
      willRespondWith: {
        status: 200,
        headers: { 'Content-Type': 'application/json' },
        body: { id: 1, name: 'Test' }
      }
    });
  });
๐Ÿ’ป

Example

This example shows a simple consumer test using Pact in JavaScript. It defines an expected GET request and response, then verifies the provider against this contract.

javascript
const { Pact } = require('@pact-foundation/pact');
const axios = require('axios');

const provider = new Pact({
  consumer: 'MyConsumer',
  provider: 'MyProvider',
  port: 1234
});

describe('Pact with MyProvider', () => {
  beforeAll(() => provider.setup());
  afterAll(() => provider.finalize());

  beforeEach(() => {
    return provider.addInteraction({
      state: 'user exists',
      uponReceiving: 'a request for user data',
      withRequest: {
        method: 'GET',
        path: '/user/1'
      },
      willRespondWith: {
        status: 200,
        headers: { 'Content-Type': 'application/json' },
        body: { id: 1, name: 'Alice' }
      }
    });
  });

  it('fetches user data', async () => {
    const response = await axios.get('http://localhost:1234/user/1');
    expect(response.status).toBe(200);
    expect(response.data).toEqual({ id: 1, name: 'Alice' });
  });

  afterEach(() => provider.verify());
});
Output
PASS Pact with MyProvider โœ“ fetches user data (xx ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total
โš ๏ธ

Common Pitfalls

  • Not running provider verification: Always run provider.verify() after consumer tests to ensure the provider matches the contract.
  • Ignoring provider states: Use state to set up provider data; missing this can cause test failures.
  • Hardcoding URLs: Use dynamic ports or environment variables to avoid conflicts.
  • Mismatch in request/response details: Ensure headers, body, and status codes exactly match between consumer and provider.
javascript
/* Wrong: Missing provider.verify() call */

// After consumer test
// provider.verify() is not called, so provider contract is not checked

/* Right: Include provider.verify() */

afterEach(() => provider.verify());
๐Ÿ“Š

Quick Reference

ConceptDescription
PactLibrary to create and verify contracts between consumer and provider
Consumer TestDefines expected requests and responses from the consumer side
Provider VerificationChecks the provider service against the saved contract
StateSets provider data or conditions before interaction
InteractionDefines one request and expected response pair
โœ…

Key Takeaways

Define expected interactions in consumer tests using Pact to create contracts.
Always run provider verification to ensure the provider meets the contract.
Use provider states to prepare data and avoid test failures.
Match request and response details exactly between consumer and provider.
Avoid hardcoding ports and URLs; use configuration for flexibility.