Create your own
Lesson illustration

Consumer-Driven Contracts with Spring Cloud Contract

Welcome to the fifth lesson in our module on testing strategies. In our previous lesson, we established the "what" and "why" of Consumer-Driven Contract Testing (CDCT). We learned that it's a powerful technique to ensure microservices can evolve independently without breaking each other, providing fast and reliable feedback directly in the CI/CD pipeline.

Today, we transition from theory to practice. Your learning outcome is to write a consumer-driven contract for a REST API using Spring Cloud Contract. We will focus on the role of the provider (or producer) service—the service that exposes an API. You'll learn how to define a contract that specifies your API's behavior and how to automatically verify that your implementation adheres to it. This is a fundamental skill for building resilient microservices and a common topic in senior engineering interviews.

1. The Producer's Role in Contract Testing

As a quick refresher, the "consumer-driven" workflow places the responsibility of defining the contract on the consumer. However, the contract is physically stored and verified within the producer's codebase.

Consumer-Driven Contract Testing Concept
This diagram contrasts traditional end-to-end testing with the CDC approach. The producer generates stubs from a contract, which the consumer then uses for isolated integration testing. Our focus today is on the producer's side: how we define the contract and set up the verification that enables this entire workflow.

The producer's responsibilities are:

  1. To host the contract files (typically written by consumer teams or in collaboration with them).
  2. To run verification tests that prove the API implementation satisfies every contract.
  3. To publish "stubs" (mock versions of the API) based on the verified contracts, which consumers can then use.

If the producer's verification tests pass, they can deploy with high confidence that they haven't broken any known consumer expectations.

2. Setting Up the Producer Project

To get started on the producer side, you need to add the Spring Cloud Contract Verifier to your Spring Boot project. This involves two key modifications to your pom.xml (if you are using Maven).

  1. Add the Verifier Dependency: This library provides the necessary classes for the contract verification logic.

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-contract-verifier</artifactId>
        <scope>test</scope>
    </dependency>
    
  2. Add the Maven Plugin: This plugin is the engine of Spring Cloud Contract. It automatically generates JUnit tests from your contract files during the build process.

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-contract-maven-plugin</artifactId>
                <!-- Add version and configuration -->
                <extensions>true</extensions>
                <configuration>
                    <!-- We'll define this base class in the next step -->
                    <baseClassForTests>
                        com.example.producerservice.BaseContractTest
                    </baseClassForTests>
                </configuration>
            </plugin>
        </plugins>
    </build>
    

    The baseClassForTests property is crucial. It tells the plugin which class the auto-generated tests should extend. This allows us to share setup logic, which we'll see shortly.

3. Writing Your First Contract

Contracts in Spring Cloud Contract are typically written using a Groovy-based Domain-Specific Language (DSL). This DSL provides a clear and readable way to define the expected request and response of an API interaction.

Contracts are placed in src/test/resources/contracts/.

Let's watch how to write a simple contract for an endpoint that returns a list of customers.

Spring Cloud Contract (HTTP)

This video from the official SpringDeveloper channel demonstrates how to write a contract using the Groovy DSL. It clearly shows the structure and syntax for defining a request and its expected response.

Watch the segment from 23:41 to 26:51. Pay close attention to the structure: the Contract.make block, the request section (defining method and URL), and the response section (defining status, headers, and body).

As you saw, the structure is quite intuitive. Let's break down an example contract file, which we might name shouldReturnAllCustomers.groovy and place in src/test/resources/contracts/.

import org.springframework.cloud.contract.spec.Contract

Contract.make {
    description "Should return all customers"
    
    // 1. Define the request the consumer will make
    request {
        method 'GET'
        url '/customers'
    }

    // 2. Define the response the producer must return
    response {
        status 200 // HTTP 200 OK
        headers {
            contentType(applicationJson())
        }
        body([
            [
                id: 1L,
                name: "Jane"
            ],
            [
                id: 2L,
                name: "Bob"
            ]
        ])
    }
}

This contract specifies that a GET request to /customers must return an HTTP 200 status with a JSON array containing two specific customer objects.

For a more detailed textual reference on the Groovy DSL and the overall setup, the official Spring guide is an excellent resource.

Getting Started | Consumer Driven Contracts

Let's review the official Spring 'Getting Started' guide, which provides another example of a contract and the surrounding code.

Read the section 'Create the contract of the REST service'. It shows a similar Groovy contract for a /person/1 endpoint. Note how it defines the request and response body.

Test your understanding!

A consumer needs to fetch a single product by its ID. The requirement is:

  • Request: A GET request to /products/{id} where id is a positive number.
  • Response: An HTTP 200 OK status with a JSON body containing productId (the same ID from the URL), productName (a string), and stock (an integer greater than or equal to 0).

Write the Groovy contract for a successful retrieval of a product with ID 123.

Show answer

A possible contract file, named shouldReturnProductById.groovy, would look like this:

import org.springframework.cloud.contract.spec.Contract
import org.springframework.cloud.contract.verifier.util.ContractVerifierUtil

Contract.make {
    description "Should return a product by its ID"
    
    request {
        method 'GET'
        // Using a dynamic value for the ID
        urlPath '/products/123' 
    }

    response {
        status 200
        headers {
            contentType(applicationJson())
        }
        body(
            productId: 123,
            productName: "Super Widget",
            stock: 100
        )
    }
}

Note: In more advanced contracts, you can use regular expressions and dynamic values from the request in the response to make contracts more flexible, but for now, hardcoded examples are perfectly fine.

4. Creating the Base Test Class

The contract defines what the API should do. Now we need to connect it to our actual Spring Boot application code. This is where the base test class comes in. The auto-generated tests will extend this class, inheriting its setup.

The purpose of the base class is to:

  • Start a slice of the Spring application context needed for the test (e.g., just the web layer).
  • Mock any dependencies that are not part of the component under test. For a REST controller, this usually means mocking the service or repository layer.
  • Provide the mocked data that the controller will return, which should match what's defined in the contract's response block.

Spring Cloud Contract (HTTP)

The SpringDeveloper video continues by demonstrating how to create this essential base class. This is a critical piece of the puzzle that connects the abstract contract to your real code.

Watch the segment from 26:51 to 30:28. Observe how it uses @MockBean to mock the CustomerRepository and Mockito.when() to define the behavior of the mock, ensuring it returns the data expected by the contract.

Here's an example of what BaseContractTest.java would look like for our customer service scenario. This should feel familiar to how you write standard Spring Boot integration tests for your controllers.

import io.restassured.module.mockmvc.RestAssuredMockMvc;
import org.junit.jupiter.api.BeforeEach;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;

import java.util.Arrays;

// Tells Spring Boot how to load the application context for the test
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
public abstract class BaseContractTest {

    @Autowired
    private CustomerRestController customerRestController; // The controller we want to test

    @MockBean
    private CustomerRepository customerRepository; // The dependency we want to mock

    @BeforeEach
    public void setup() {
        // Mock the repository call to return the data defined in our contract
        Mockito.when(customerRepository.findAll())
               .thenReturn(Arrays.asList(
                   new Customer(1L, "Jane"),
                   new Customer(2L, "Bob")
               ));

        // Set up RestAssured to make mock MVC calls to our controller
        RestAssuredMockMvc.standaloneSetup(customerRestController);
    }
}

With this in place, the auto-generated test will call the /customers endpoint on customerRestController. The controller will then call customerRepository.findAll(), which—thanks to our mock—will return the exact list of customers we defined. The test then asserts that the HTTP response matches the response block of the contract.

5. Running the Verification and Seeing the Payoff

Now for the magic. You don't write the final test yourself. The plugin does it for you. Simply run your standard build command:

mvn clean install

During the build, the Spring Cloud Contract plugin will:

  1. Scan src/test/resources/contracts/.
  2. For each Groovy file, generate a JUnit 5 test class in target/generated-test-sources/contracts/.
  3. Compile and run these generated tests along with your other tests.

Spring Cloud Contract (HTTP)

Let's see this in action and, more importantly, see what happens when a contract is broken. This demonstrates the immediate value of CDCT as a safety net.

Watch from 30:28 to 33:12. The presenter first runs a successful build. Then, he intentionally breaks the contract by changing the data in the mock. Notice how the build fails with a clear error message, pinpointing the mismatch between the expected and actual response.

This is the key benefit. If a developer on the producer team accidentally changes a field name, modifies a data type, or alters the response structure in a way that violates the contract, the build fails immediately. This automated check prevents breaking changes from ever being deployed, providing the safety and confidence needed for independent releases.

Conclusion

In this lesson, you've learned the practical steps to implement the producer side of a consumer-driven contract test with Spring Cloud Contract. This is a powerful pattern for building robust and maintainable microservice architectures.

Key Takeaways:

  • Setup: You need the spring-cloud-starter-contract-verifier dependency and the spring-cloud-contract-maven-plugin.
  • Contract Definition: Contracts are written in a Groovy DSL and placed in src/test/resources/contracts/. They define the request a consumer makes and the response the producer must provide.
  • Base Test Class: An abstract base class is used to set up the Spring test context and mock dependencies (@MockBean), ensuring the controller returns the data specified in the contract.
  • Verification: Running mvn clean install automatically generates and executes tests that verify your controller's implementation against the contract, failing the build if there's a mismatch.

Next Up

You have now successfully defined and verified a contract on the producer side. The next logical step is to see how this contract is used by the consumer. In the following lesson, we will implement a provider-side verification test based on a contract generated by Spring Cloud Contract, and then we will explore how a consumer can use the generated stubs to test its integration in complete isolation.

Can't find a good explanation? Sign up and we'll make it for you

Sign up