Consumer-Driven Contracts with Spring Cloud Contract

Last Updated:  August 27, 2021 | Published: May 26, 2019

Fetching data from other systems is common in a microservice-based architecture. Besides the downtime of the other service, a broken API contract is one of the worst things that can happen. As a client of an external API, you must trust that the data contract is stable and changes are made known in advance. When it comes to testing the interaction with the external service, you can mock the response of the targeted service using frameworks like WireMock. Manually writing mock results can become quite cumbersome if you call many services and if the response is rather big. Luckily there is a nice solution to tackle these two tasks: Consumer-Driven Contracts with Spring Cloud Contract.

With this blog post, you'll learn how to use and write consumer-driven contracts with Spring Cloud Contract using Java 11, Spring Boot 2.4.6, JUnit 5, and Spring Cloud 2020.0. 

Introduction to Spring Cloud Contract

Spring promotes this umbrella project with the following features:

  • ensure that HTTP / Messaging stubs (used when developing the client) are doing exactly what actual server-side implementation will do
  • promote acceptance test driven development method and Microservices architectural style
  • to provide a way to publish changes in contracts that are immediately visible on both sides of the communication
  • generate boilerplate test code used on the server side

To demonstrate these features of Spring Cloud Contract, we'll work with a system architecture that contains two services: the book-store-server (aka.  producer) and the book-store-client (aka. consumer).

The book store client fetches all available books from the book store server endpoint /books. During integration tests for the client, we'll use a stub of the book server that we'll automatically generate with Spring Cloud Contract to ensure the API contract:

Setting Up the Server-Side (Book Store Server)

The required Maven dependencies for the book store server are the following:

For demonstration purposes, we'll keep our REST endpoint for this example quite simple:

With the help of Java Faker, ourBookService returns ten random books:

Creating Contracts with Spring Cloud Contract

What's next is to define the contracts for our  /books API endpoint. As part of these contracts, we define the specifications of our endpoint and how the response (i.e., API contract) looks like.

We can define these contracts with either Groovy or YAML. By default, Spring Cloud Contract expects our contract files as part of the folder src/test/resources/contracts.

For this example, we'll create one contract test that will make sure a GET call to /books returns the given data as application/json with an HTTP 200 status. Therefore we'll create a shouldReturnBooks.groovy file and declare the contract:

For Spring Cloud Contract to automatically generate the contracts test, we'll need a base test class later on. As part of this base class, we define our test setup and configure RestAssuredMockMvc for our controllers.

We'll mock any collaborator of our controller classes with Mockito:

Next, we need to point to this base class as part of the Spring Cloud Contract Maven plugin configuration:

During the build step (after the maven-compiler-plugin), the Maven plugin will generate a test based on this contract under target/generated-test-sources/contracts and execute this test during the test step.

In addition a book-store-server-0.0.1-SNAPSHOT-stubs.jar is created, containing the WireMock stub:

We have to make sure to run  mvn install so that the created .jar files (especially the stub jar) are installed in our local Maven (.m2 folder) repository.

To make these stubs available for other developers/the internet, we can upload the XYZ-stubs.jar to, e.g., Artifactory or Maven Central during our CI pipeline run.

Setting Up the Client-Side for Spring Cloud Contract

The setup for the client-side is a little bit different:

Instead of the spring-cloud-contract-verifier, the spring-cloud-contract-stub-runner is used here. Note that we don't have to include the XYZ-stubs.jar  in our project to make use of it.

We're using the Spring WebFlux WebClient to fetch all available books from our book-store-server (producer):

For the sake of simplicity, the method returns the data as JsonNode (Jackson's JSON representation).

What's left is to now write a test that uses the automatically generated stub of our producer to verify our client works with given the API contract:

The @AutoConfigureStubRunner annotation takes an array of strings for its ids attribute to reference the required XYZ-stubs.jar files with a port definition for the WireMock server.

The stubsMode attribute defines how these .jars are loaded: either from the classpath (requires to add them to the Maven project), local (local .m2/repository folder) or remote (using your configured Maven repositories). During test execution, the WireMock server will respond with the defined data in the contract on the server-side, and the test will pass.

For more detailed information about consumer-driven contracts with Spring Cloud Contract, have a look at the following resources:

For more in-depth content and tips about testing Spring Boot applications, consider enrolling in the Testing Spring Boot Applications Masterclass.

You can find the code for the book store server and client on GitHub and further Spring-related resources on my blog.

Have fun writing consumer-driven contracts,

Phil

>