CodeGym /Courses /Module 5. Spring /Lecture 266: Introduction to contract testing (Pact)

Lecture 266: Introduction to contract testing (Pact)

Module 5. Spring
Level 21 , Lesson 5
Available

Let's get straight to the point. When microservices talk to each other, they're basically like two developers who agreed: "I'll send you a JSON file with these fields!". In contract testing that "agreement" is called a contract. The idea is that interactions between services are validated against predefined expectations so nobody accidentally breaks the "deal".

Example: Imagine you're working on the "Customer" microservice that makes HTTP requests to the "Products" microservice. "Products" replies with JSON containing product info. You can write contract tests to make sure "Products" always returns the JSON shape the "Customer" expects.

Why does this matter?

In a microservices architecture where each service is independent but interacts with others, changing one part can easily break another. For example, if the team owning "Products" suddenly tweaks the JSON structure, "Customer" might start crashing. Contract testing helps prevent that:

  • It guarantees stable interactions between services.
  • It saves time because integration issues are caught early in development.
  • It reduces manual checks since interactions are tested automatically.

Difference between integration and contract testing

Integration tests check how multiple components work together, while contract tests focus on the exact expected inputs and outputs between two services. Contract testing is useful because it lets you test interactions in isolation without bringing up both services.


Basics of working with Pact

Pact is a popular tool for contract testing. It acts as the middleman between two parties:

  • Consumer (consumer) — the service that sends requests.
  • Provider (provider) — the service that responds to requests.

Pact lets you create and verify contracts between those parties.

Core idea of how Pact works

  1. Creating the contract: the consumer creates a contract that describes which requests it will make and which responses it expects from the provider.
  2. Verifying the contract: the provider verifies the contract to ensure it can respond correctly to the consumer's requests.

Example workflow:

  1. The "Customer" service generates a contract that says:
    • I'll send a GET request to /products/1
    • I expect a JSON response with fields id, name, price
  2. The contract is passed to the "Products" service.
  3. The "Products" service uses Pact to verify it can return such JSON.

Example of using Pact for microservices

Let's get practical. Imagine we have two microservices:

  • Consumer: the "Customer" service that requests product info.
  • Provider: the "Products" service that returns product info.

Step 1: Create the contract on the consumer side

We start with the "Customer". In this service we describe which request and response we expect from "Products".

First, add the Pact library to our build.gradle:

dependencies {
    testImplementation 'au.com.dius.pact.consumer:junit5:4.5.7'
}

Writing a test using Pact


@PactTestFor(providerName = "ProductService")
public class ProductConsumerContractTest {

    @Pact(consumer = "CustomerService")
    public RequestResponsePact createPact(PactDslWithProvider builder) {
        return builder
                .given("Product with ID 1 exists")
                .uponReceiving("A request for product with ID 1")
                .path("/products/1")
                .method("GET")
                .willRespondWith()
                .status(200)
                .body("{\"id\": 1, \"name\": \"Laptop\", \"price\": 1200.00}")
                .toPact();
    }

    @Test
    @PactTestFor(pactMethod = "createPact")
    public void testConsumerBehaviour(MockServer mockServer) {
        // Use the mockServer to emulate the "Products" API
        String response = new RestTemplate().getForObject(mockServer.getUrl() + "/products/1", String.class);

        // Check that the response matches the expectation
        assertEquals("{\"id\": 1, \"name\": \"Laptop\", \"price\": 1200.00}", response);
    }
}

Explanation:

  • We create a contract using Pact.
  • The contract states that the consumer (CustomerService) makes a GET request to /products/1 and expects a specific JSON response.
  • Pact spins up a MockServer to test the consumer behavior.

Step 2: Verify the contract on the provider side

Now we pass the contract to the "Products" service and check that it meets the expectations.

Add Pact to the provider service:

dependencies {
    testImplementation 'au.com.dius.pact.provider:junit5:4.5.7'
}

Contract verification


@Provider("ProductService")
@PactBroker(host = "localhost", port = "9292")  // Pact broker for storing contracts
public class ProductProviderContractTest {

    @TestTemplate
    @ExtendWith(PactVerificationInvocationContextProvider.class)
    public void validatePacts(PactVerificationContext context) {
        context.verifyInteraction();
    }

    @State("Product with ID 1 exists")
    public void productExists() {
        // Set up test database or mocks for the state "Product with ID 1 exists"
    }
}

Explanation:

  • We use Pact to verify the contract.
  • In the productExists method we set the initial state (for example, insert a record into the database).
  • Pact automatically checks that the "Products" service conforms to the contract.

Benefits of using Pact

  1. Catch problems early: integration issues between consumer and provider become apparent right away.
  2. Test isolation: consumer and provider can be tested independently.
  3. Interaction documentation: contracts act as auto-documentation for the API.

Wrapping up

Contract testing is the superhero of microservice interactions. A tool like Pact helps your teams sleep better, knowing that updates to one service won't break another. In the next lectures we'll dive deeper into practical Pact usage and write our first contract tests for microservice interactions.

Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION