Back to Reference
Layer 3Microservices

Layer 3: Contract & Integration Testing

Pact contracts and integration tests verify cross-service correctness.

The microservice testing challenge

In a microservice architecture, bugs surface at the boundaries between services. A field is renamed in one service's response; the consuming service breaks. An optional field becomes required; three downstream services fail silently. A new enum value is added; consumers that don't handle it crash.

Traditional integration tests address this by deploying multiple services together and testing their interactions end-to-end. But these tests are slow (minutes to start environments), brittle (any service outage breaks the whole suite), and expensive (requires full infrastructure).

Contract testing verifies that services remain compatible without requiring them to run together. Each service tests against a contract — a formal specification of what it expects and what it provides. If the contracts are compatible, the services will work together in production.

Consumer-Driven Contracts

The Consumer-Driven Contract Testing (CDCT) pattern, implemented by Pact, inverts the traditional testing direction: instead of the provider defining the API and consumers adapting to it, consumers declare what they need, and providers verify they still satisfy those needs.

How Pact works

  1. Consumer test — The consumer writes a test that describes the HTTP request it will make and the response it expects. Pact records this as a contract (pact file).
  2. Pact Broker — The contract is published to a central Pact Broker, which stores all contracts and tracks verification status.
  3. Provider verification — The provider runs the contract against its actual implementation. If the provider satisfies all consumer expectations, verification passes.
  4. Can I Deploy? — Before deploying, services query the Pact Broker: "are all my contracts verified against the version I'm deploying to?" Only deploy if the answer is yes.
// Consumer side: Order Service expects User Service response
@Pact(consumer = "OrderService", provider = "UserService")
public V4Pact userDetailsPact(PactDslWithProvider builder) {
    return builder
        .given("user with ID 42 exists")
        .uponReceiving("a request for user details")
        .path("/users/42")
        .method("GET")
        .willRespondWith()
        .status(200)
        .body(newJsonBody(body -> {
            body.stringType("name", "Alice");
            body.stringType("email", "alice@example.com");
            body.booleanType("active", true);
        }).build())
        .toPact(V4Pact.class);
}

@Test
@PactTestFor(pactMethod = "userDetailsPact")
void shouldFetchUserDetails(MockServer mockServer) {
    UserClient client = new UserClient(mockServer.getUrl());
    UserDetails user = client.getUser(42L);

    assertThat(user.getName()).isEqualTo("Alice");
    assertThat(user.isActive()).isTrue();
}

💡 Catching issues earlier

eBay's Notification Platform team adopted Pact contracts and reported that the time to detect API compatibility issues dropped from days to minutes — issues caught in isolated tests before services ever interact in staging. (Source: eBay Innovation Blog)

Spring Cloud Contract

For teams fully within the Spring ecosystem, Spring Cloud Contract provides a Spring-native alternative to Pact. It supports both HTTP and messaging contracts, with tight integration into Spring Boot's testing infrastructure.

Key features

// Contract definition (Groovy DSL)
Contract.make {
    description "should return user details"
    request {
        method GET()
        url "/users/42"
    }
    response {
        status OK()
        headers {
            contentType applicationJson()
        }
        body([
            name: "Alice",
            email: "alice@example.com",
            active: true
        ])
    }
}

From this single contract definition, Spring Cloud Contract generates both a provider verification test and a WireMock stub JAR that consumers use in their tests. The contract is the single source of truth.

When to use which

Tool Best for Strengths
Pact Polyglot, many consumers Language-agnostic, Pact Broker for governance, "Can I Deploy?" workflow, mature ecosystem
Spring Cloud Contract Spring ecosystem Deep Spring Boot integration, auto-generated stubs and tests, messaging support
Specmatic OpenAPI-first teams Uses OpenAPI spec as contract, no separate contract language, schema-first development

Choosing a tool

If your services span multiple languages (Java, Python, Node.js), use Pact — it has client libraries for 12+ languages. If you're all-Spring, Spring Cloud Contract offers tighter integration and less ceremony. If you already maintain OpenAPI specs, Specmatic eliminates the need for a separate contract definition.

Integration testing strategies

Contract tests verify API compatibility, but they do not test full business flows. You still need integration tests — the question is how many and at what level.

The testing diamond

The Spotify honeycomb model (or "testing diamond") inverts the traditional testing pyramid for microservices. Instead of a wide base of unit tests, the emphasis shifts to:

For domain-heavy services (pricing, rules engines), the traditional pyramid still works: most value comes from unit-level property-based tests. For thin services (API gateways, orchestrators), the diamond is more appropriate: most logic is in the integration, not the computation.

Performance and load testing

Performance testing ensures that services meet latency and throughput requirements under load. Two tools dominate the modern landscape:

k6

Gatling

Both tools integrate into CI pipelines as quality gates: define latency thresholds (p99 < 200ms) and throughput minimums, and fail the build if they're not met. Run performance tests on a schedule (nightly or weekly) rather than on every PR to avoid blocking development velocity.

CI/CD Integration

At scale, running all tests on every change is not feasible. Modern CI strategies use intelligence to select the right tests:

Test Impact Analysis (TIA)

Spotify's test infrastructure manages 50,000+ tests across hundreds of services. Test Impact Analysis maps code changes to the tests that exercise them, running only the relevant subset. A change to the pricing module runs pricing tests, not the entire suite.

Predictive Test Selection

Launchable and similar tools use machine learning to predict which tests are most likely to fail given a code change. Teams report up to 80% reduction in test execution time with minimal risk of missing failures. The model learns from historical change-to-failure correlations.

Parallelization strategies

Combined, these strategies achieve 70-90% reduction in pipeline duration while maintaining the same defect detection capability. The key is layering: run fast deterministic tests first (architecture, unit), then contracts, then integration, with each layer gating the next.

eBay case study

eBay adopted consumer-driven contract testing to manage API evolution across its marketplace platform. With hundreds of internal services and frequent API changes, integration test environments were a constant bottleneck.

After introducing Pact-based contract testing:

💡 Contract testing as a cultural shift

eBay's experience highlights that contract testing is not just a technical practice — it's a cultural shift. Providers must treat consumer contracts as commitments. Breaking a contract is breaking a promise to another team. The Pact Broker makes these commitments visible and enforceable.