Introduction to Spring Cloud Contract, Part One

After we have finished this webinar, we:

  • Are familiar with the basic concepts of the Consumer-Driven Contracts approach.
  • Can identify the key features of Spring Cloud Contract.
  • Can create a Maven project for a REST producer service.
  • Can create our first consumer driven contract.
  • Understand how we can create a REST producer service that fulfills our contract.

Let’s start by taking a quick look at consumer-driven contracts.

Get the source code from Github

A Very Quick Introduction to Consumer-Driven Contracts

The consumer-driven contracts approach helps us to write applications which use the microservices architecture. When we are writing an application by using the consumer-driven contracts approach, we have to implement the following components:

  • A provider is a component that provides a public API which can be consumed by other components.
  • A consumer is a components that consumes the API provided by a provider.
  • A contract specifies an interaction between a consumer and a provider. To be more specific, it specifies the response that should be returned by the provider when it receives the request specified by the contract.
The consumer-driven contracts approach is a quite complex topic, and it’s impossible to cover the details in this webinar. If you want to get more information the consumer-driven contracts approach, I recommend that you take a look at the following article: Consumer-Driven Contracts: A Service Evolution Pattern .

Next, we will take a quick look at the key features of Spring Cloud Contract.

The Key Features of Spring Cloud Contract

The Spring Cloud Contract project integrates different technologies with each other, and its goal is to help us to write applications by using consumer-driven contracts. Its key features are:

  • We can create contracts by using Groovy DSL or Yaml.
  • Spring Cloud Contract generates test classes which help us to ensure that our producers honor the created contracts.
  • Spring Cloud Contract generates stubs which provide test data that allows us to write tests for our consumers. In other words, these stubs help us to ensure that our consumers can consume the data returned by our producers.
If you want to get more information about the features of Spring Cloud Contract, you should take a look at the website of Spring Cloud Contract project .

Let’s move on and find out how we can create a Maven project for a REST producer service.

Creating a Maven Project for a REST Producer Service

Because our example application uses Spring Boot, our Maven project is pretty much a normal Spring Boot project. However, there are three things I want point out:

First, we have to add the Spring Cloud Contract BOM to our Maven build. This BOM specifies the required dependencies of Spring Cloud Contract. We can do this by adding the following snippet to our pom.xml file:

 1<dependencyManagement>
 2    <dependencies>
 3        <dependency>
 4            <groupId>org.springframework.cloud</groupId>
 5            <artifactId>spring-cloud-contract-dependencies</artifactId>
 6            <version>2.0.1.RELEASE</version>
 7            <type>pom</type>
 8            <scope>import</scope>
 9        </dependency>
10    </dependencies>
11</dependencyManagement>

Second, because we want to create contracts and generate test classes automatically, we have to declare the spring-cloud-starter-contract-verifier dependency in our POM file. We can do this by adding the following snippet to the dependencies section of our POM file:

1<dependency>
2    <groupId>org.springframework.cloud</groupId>
3    <artifactId>spring-cloud-starter-contract-verifier</artifactId>
4    <scope>test</scope>
5</dependency>

Third, we have configure the Spring Cloud Contract Maven plugin. This plugin generates tests from the provided contracts and creates stubs that can be used to test the consumer which consumes our REST API. We can configure this plugin by following these steps:

  1. Add the Spring Cloud Contract Maven Plugin to our build.
  2. Ensure that this plugin can add the required libraries to the core class loader. This is a crucial step. If we don’t do this, the Spring Cloud Contract Maven Plugin cannot generate test classes or create the required stubs.
  3. Configure the fully qualified class name of the base class that is extended by the generated test classes.

After we have configured the Spring Cloud Contract Maven plugin, its configuration looks as follows:

1<plugin>
2    <groupId>org.springframework.cloud</groupId>
3    <artifactId>spring-cloud-contract-maven-plugin</artifactId>
4    <version>2.0.1.RELEASE</version>
5    <extensions>true</extensions>
6    <configuration>
7        <baseClassForTests>com.testwithspring.master.webinar.BaseTest</baseClassForTests>
8    </configuration>
9</plugin>

We have now created a Maven project that can compile and run our REST product service. Next, we will implement our REST product service.

Implementing a REST Producer Service

When we want to implement a REST API that returns the information of a single task, we have to follow these steps:

First, we have to create a class that contains the information of the returned task. After we have written the Task class, its source code looks as follows:

 1public class Task {
 2
 3    private final Long id;
 4    private final String title;
 5
 6    public Task(Long id, String title) {
 7        this.id = id;
 8        this.title = title;
 9    }
10
11    //Getters are omitted
12}

Second, we have to implement a service that returns the information of the requested task. Because this is just an example, we can write this service by following these steps:

  1. Add a Map field to our service class. This field contains the tasks returned by our service.
  2. Implement a constructor that initializes this field and adds two tasks to our map.
  3. Write a findById() method that gets the requested task from our map.

After we have written our service class, its source code looks as follows:

 1import org.springframework.stereotype.Service;
 2
 3import java.util.HashMap;
 4import java.util.Map;
 5
 6@Service
 7class TaskService {
 8
 9    private final Map<Long, Task> tasks;
10
11    TaskService() {
12        tasks = new HashMap<>();
13        tasks.put(1L, new Task(1L, "Write REST service"));
14        tasks.put(2L, new Task(2L, "Write REST client"));
15    }
16
17    Task findById(Long id) {
18        return tasks.get(id);
19    }
20}

Third, before we will write the controller that implements our REST API, we have to create the contract that specifies its behavior. When we create this contract, we have to follow these two rules:

  1. We have to put our contracts to the src/test/resources/contracts directory.
  2. We can specify our contracts by using either Groovy DSL or Yaml.

We can now create our contract by following these steps:

  1. Create a new contract by calling the static make() method of the Contract class.
  2. Provide a description for our contract.
  3. Identify the HTTP request that triggers our contract. In our case, our contract is valid when our REST API receives a GET request to the path: ‘/api/task/1’.
  4. Specify the HTTP response that is returned by our REST API. In this case, our REST API must return the information of the found task as JSON.

After we have written our contract (find_task_by_id.groovy) by using Groovy, it looks as follows:

 1import org.springframework.cloud.contract.spec.Contract
 2
 3Contract.make {
 4    description 'should return a task when the id of the requested task is 1'
 5
 6    request {
 7        url '/api/task/1'
 8        method GET()
 9    }
10
11    response {
12        status 200
13        headers {
14            contentType applicationJson()
15        }
16        body (
17                id: 1,
18                title: 'Write our first contract'
19        )
20    }
21}

When we compile our project, the Spring Cloud Contract Maven plugin creates a test class which tests our REST API by using Rest Assured. This test class extends the BaseTest class, and its source code looks as follows:

 1import com.jayway.jsonpath.DocumentContext;
 2import com.jayway.jsonpath.JsonPath;
 3import com.testwithspring.master.webinar.BaseClass;
 4import io.restassured.module.mockmvc.specification.MockMvcRequestSpecification;
 5import io.restassured.response.ResponseOptions;
 6import org.junit.Test;
 7
 8import static com.toomuchcoding.jsonassert.JsonAssertion.assertThatJson;
 9import static io.restassured.module.mockmvc.RestAssuredMockMvc.*;
10import static org.springframework.cloud.contract.verifier.assertion.SpringCloudContractAssertions.assertThat;
11
12public class WebinarTest extends BaseTest {
13
14	@Test
15	public void validate_find_task_by_id() throws Exception {
16		// given:
17			MockMvcRequestSpecification request = given();
18
19		// when:
20			ResponseOptions response = given().spec(request)
21					.get("/api/task/1");
22
23		// then:
24			assertThat(response.statusCode()).isEqualTo(200);
25			assertThat(response.header("Content-Type"))
26					.matches("application/json.*");
27		// and:
28			DocumentContext parsedJson = JsonPath.parse(
29					response.getBody().asString()
30			);
31			assertThatJson(parsedJson).field("['id']")
32					.isEqualTo(1);
33			assertThatJson(parsedJson).field("['title']")
34					.isEqualTo("Write our first contract");
35	}
36}

Fourth, we have to write the controller class that processes GET requests send to the path: ‘/api/task/{id}’. This controller gets the returned task by invoking the findById() method of the TaskService class.

After we have written our controller class, its source code looks as follows:

 1import org.springframework.web.bind.annotation.GetMapping;
 2import org.springframework.web.bind.annotation.PathVariable;
 3import org.springframework.web.bind.annotation.RestController;
 4
 5@RestController
 6class TaskController {
 7
 8    private final TaskService service;
 9
10    TaskController(TaskService service) {
11        this.service = service;
12    }
13
14    @GetMapping("/api/task/{id}")
15    Task findById(@PathVariable("id") Long id) {
16        return service.findById(id);
17    }
18}

Fifth, we have to write an abstract BaseTest class which configures the system under test by using a setup method that is run before a test method is run. We can implement this setup method by following these steps:

  1. Create a new TaskService mock.
  2. Create a new MockMvc object by using the standalone configuration. We must remember to ensure that the created object supports REST Assured. Also, we must register the tested controller.
  3. Ensure that our mock object returns the information of the found task when it is invoked by using the method parameter 1L.

After we have written our setup method, the source code of the BaseTest class looks as follows:

 1import io.restassured.module.mockmvc.RestAssuredMockMvc;
 2import org.junit.Before;
 3import org.junit.runner.RunWith;
 4import org.springframework.beans.factory.annotation.Autowired;
 5import org.springframework.boot.test.context.SpringBootTest;
 6import org.springframework.boot.test.mock.mockito.MockBean;
 7import org.springframework.test.context.junit4.SpringRunner;
 8
 9import static org.mockito.BDDMockito.given;
10import static org.mockito.Mockito.mock;
11
12public abstract class BaseTest {
13
14    @Before
15    public void setup() {
16        TaskService service = mock(TaskService.class);
17
18        RestAssuredMockMvc.standaloneSetup(new TaskController(service));
19        given(service.findById(1L))
20                .willReturn(new Task(1L, "Write our first contract"));
21    }
22}
By the way, we can also write a base class that loads the application context of our Spring Boot application. This allows us to test our REST API by using integration tests. For example, we can use DbUnit and read information from the real database.

When we run our tests, we see that they pass. We have now written a producer service that fulfills our contract. Let’s summarize what we learned from this webinar.

Summary

This webinar has taught five things:

  • Contracts specify the interactions between a producer and a consumer.
  • We can create contracts by using Gradle DSL or Yaml.
  • The Spring Cloud Contract Maven Plugin generates both test classes and producer stubs.
  • The tests classes generated by the Spring Cloud Contract Maven Plugin extend a base class that must be implemented by us.
  • The base class can use the standalone configuration or it can load the Spring application context. In other words, we can write either unit or integration tests for our producer.

Get the source code from Github