Skip to content

Testing Microservices Integration — Consumer-Driven Contract Tests Guide

DodaTech Updated 2026-06-24 4 min read

In this tutorial, you'll learn about Testing Microservices Integration. We cover key concepts, practical examples, and best practices.

Microservices integration testing is the most challenging layer of the test pyramid — services depend on other services, each with its own deployment cycle, data store, and team ownership. In this guide, you will learn how to test microservice integrations using consumer-driven contracts with Pact, virtualize dependent services, and build integration test suites that run in CI without fragile end-to-end environments. The DodaTech backend platform uses Pact contracts between all 15 microservices, enabling independent deployment with zero integration surprises.

Learning Path

flowchart LR
  A[Microservices Architecture] --> B[Integration Testing]
  B --> C[Contract Testing
You are here] C --> D[Consumer-Driven Contracts] D --> E[Independent Deployment] style C fill:#f90,color:#fff

The Integration Testing Challenge

Approach Speed Isolation Confidence
E2E testing Slow None High
Contract testing Fast Full Medium-High
Mock-based Fast Full Low
Service virtualization Medium Full Medium

Consumer-Driven Contract with Pact

The consumer defines its expectations first, then the provider verifies them.

Consumer Test (Python)

import atheris
import logging

# Consumer test defines the expected interaction
from pact import Consumer, Provider

pact = Consumer("WebFrontend").has_pact_with(Provider("UserService"))
pact.start_service()
pact.upon_receiving("a request for user details") \
    .with_request("get", "/users/1") \
    .will_respond_with(200, body={
        "id": 1,
        "name": "Alice",
        "email": "alice@example.com"
    })

with pact:
    result = pact.setup()
    # In real code, make HTTP call to mock server
    print("Consumer contract defined for UserService")

pact.stop_service()

Provider Verification (JavaScript)

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

describe('UserService provider verification', () => {
  it('verifies all consumer contracts', async () => {
    const verifier = new Verifier({
      provider: 'UserService',
      providerBaseUrl: 'http://localhost:3001',
      pactBrokerUrl: 'https://pact-broker.example.com',
      stateHandlers: {
        'a user exists with ID 1': async () => {
          // Set up database state
          await setupUser({ id: 1, name: 'Alice', email: 'alice@example.com' });
        },
      },
    });

    return verifier.verifyProvider();
  });
});

Expected output:

Verifying contract for WebFrontend
  ✓ returns user details
  ✓ returns 404 for non-existent user

2 interactions verified, 0 failures

Service Virtualization with WireMock

When the dependent service is not available, virtualize it:

from wiremock.client import *

def setup_mock_user_service():
    MappingBuilder(
        MappingMatchers.url_path_equal_to("/users/1"),
        Methods.GET
    ).will_return(
        ResponseDefinitionBuilder()
        .with_status(200)
        .with_header("Content-Type", "application/json")
        .with_body('{"id": 1, "name": "Alice", "email": "alice@test.com"}')
    )

    Mappings.create_mapping(
        MappingBuilder(
            MappingMatchers.url_path_matching("/users/\\d+"),
            Methods.GET
        )
    )

def test_with_virtual_service():
    response = requests.get("http://localhost:8080/users/1")
    assert response.status_code == 200
    assert response.json()["name"] == "Alice"
    print("Virtual service test passed")

Testing Asynchronous Integration (Message Queues)

Microservices often communicate via message queues:

import json, time

class MessageQueueTestClient:
    def __init__(self):
        self.messages = []

    def publish(self, topic, message):
        self.messages.append({"topic": topic, "message": message, "time": time.time()})
        return True

    def consume(self, topic, timeout=5):
        matching = [m for m in self.messages if m["topic"] == topic]
        return matching[-1] if matching else None

def test_order_processing():
    queue = MessageQueueTestClient()
    order = {"order_id": "ORD-001", "user_id": 1, "total": 99.99}

    queue.publish("orders", order)
    time.sleep(0.1)
    received = queue.consume("orders")

    assert received is not None
    assert received["message"]["order_id"] == "ORD-001"
    assert received["message"]["total"] == 99.99
    print("Async message test passed")

test_order_processing()

Integration Test in CI

name: Microservice Integration Tests
on: [pull_request]

jobs:
  contract-tests:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_PASSWORD: test
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v4

      - name: Start service
        run: docker compose up -d user-service

      - name: Run provider verification
        run: npm run pact:verify

      - name: Publish results
        if: always()
        run: npm run pact:publish

Practice Questions

1. What is consumer-driven contract testing?

The consumer defines what it expects from the provider, and the provider verifies it can meet those expectations. This catches breaking changes before deployment.

2. How does contract testing differ from end-to-end testing?

Contract testing tests one service pair in isolation (fast, precise). E2E testing tests the full system (slow, broad). Contract tests catch integration issues earlier.

3. What is the Pact Broker and why is it important?

The Pact Broker stores and versions contracts, enabling consumers and providers to coordinate contract changes across deployment cycles.

4. How do you test asynchronous microservice communication?

Mock the message broker or use an in-memory queue for unit tests. Use a real broker (Kafka, RabbitMQ) in integration tests with Docker.

Challenge: Create contracts between three microservices: Order Service, Payment Service, and Notification Service. Define consumer tests for each pair, add provider verification, and build a CI pipeline that runs all verifications and publishes results.

FAQ

What is microservices integration testing?

Integration testing for microservices verifies that services communicate correctly — matching API contracts, handling errors, and processing async messages.

How do I test microservices without deploying all of them?

Use consumer-driven contracts (Pact) for API compatibility and service virtualization (WireMock) for dependent services.

What is the difference between Pact and WireMock?

Pact creates a contract from consumer expectations that the provider verifies. WireMock stubs a service response without any provider verification.

When should I use E2E tests instead of contract tests?

Use E2E tests for critical user journeys that span multiple services. Use contract tests for all service-to-service API interactions.

What's Next

Testing Data Pipelines — ETL & Data Quality
Contract Testing — Deep Dive
Testing WebSocket Connections

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro