In a distributed system, one of the challenges is to continuously test the integration between components. Let’s take a simple challenge to test one api to api integration. For the sake of discussion, we will call them producer api and consumer api.

We could spin up both the apis to test, which we generally call integration testing. Integration testing is great, it is testing the real apis. But the producer api itself may depend on other api(s) or a database or something else. We will need to spin all of this infrastructure to spin it up, which could not only be painful, our tests will be slow.

Another option is to mock the producer api in our consumer tests. Mocking is great and the tests run fast, but there are few challenges with mocks.

Let’s say we mock the producer api in our tests and are happy about it. The producer api, like any other api will evolve, new features get added or bugs get fixed. As a consequence of this, it can start diverging from what we have mocked. When this happens, our mock tests will still pass, but the real integration between the services may break and we may not know until it is too late.

Spring Cloud Contract Verifier

Spring Cloud Contract Verifier is a tool that enables Consumer Driven Contract (CDC). It is shipped with Contract Definition Language (DSL) written in Groovy or YAML.

Let’s take a look at how we can use it. Complete source code is here in github.

Add below dependencies to the Producer api project

implementation platform ("org.springframework.cloud:spring-cloud-contract-dependencies:2.2.2.RELEASE")
implementation("org.springframework.cloud:spring-cloud-starter-contract-verifier")

name our base test class in build.gradle

contracts {
baseClassForTests = "com.phani.spring.cloudcontracts.spec.BaseContractTest"
}

create the base test class

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@DirtiesContext
@AutoConfigureMessageVerifier
public abstract class BaseContractTest {
@Autowired
private BooksController booksController;

@Before
public void setup() {
StandaloneMockMvcBuilder standaloneMockMvcBuilder
= MockMvcBuilders.standaloneSetup(booksController);
RestAssuredMockMvc.standaloneSetup(standaloneMockMvcBuilder);
}
}

create a contract using Spring Cloud Contract DSL

import org.springframework.cloud.contract.spec.Contract
Contract.make {
description "should return book for id 1"
request{
method GET()
url("/book/1")
}
response {
body(
id: 1,
name: $(anyNonBlankString()),
author: $(anyNonBlankString())
)
status 200
headers {
header 'Content-Type': applicationJson()
}
}
}

thats it! This contract will be automatically validated as part of unit tests.

In the client project, add below dependencies

testCompile group: 'org.springframework.cloud', name: 'spring-cloud-contract-wiremock', version: '2.1.1.RELEASE'
testCompile group: 'org.springframework.cloud', name: 'spring-cloud-contract-stub-runner', version: '2.1.1.RELEASE'

create a test with below annotations

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@AutoConfigureMockMvc
@AutoConfigureJsonTesters
@AutoConfigureStubRunner(
stubsMode = StubRunnerProperties.StubsMode.LOCAL,
ids = "com.phani.spring:server:+:stubs:8090")
public class BooksControllerIntegrationTests {

@Autowired
private BooksClient booksClient;

@Test
public void getReturnsAbook() throws Exception {
Book book = booksClient.getBook(1);
assertThat(book).isNotNull();
assertThat(book.getId()).isEqualTo(1);
assertThat(book.getAuthor()).isNotEmpty();
assertThat(book.getName()).isNotEmpty();
}
}

Spring Cloud Contracts will be automatically stand up a mock producer api for the unit tests.

Let’s say we inadvertently changed the type of our version field on the Book. Our contact test will fail right in the Producer api with below error, which is awesome!

java.lang.IllegalStateException: Parsed JSON [{"id":1,"name":"The Hunger Games","author":"Suzanne Collins","version":"v23"}] doesn't match the JSON path [$[?(@.['version'] =~ /-?(\d*\.\d+|\d+)/)]]
at com.toomuchcoding.jsonassert.JsonAsserter.check(JsonAsserter.java:228)
at com.toomuchcoding.jsonassert.JsonAsserter.checkBufferedJsonPathString(JsonAsserter.java:267)
at com.toomuchcoding.jsonassert.JsonAsserter.matches(JsonAsserter.java:176)
at com.phani.spring.cloudcontracts.spec.ContractVerifierTest.validate_shouldReturnBook(ContractVerifierTest.java:60)

Complete source code is here in github

Conclusion

As we have seen, Spring Cloud Contract Verifier is a great tool to ensure the integration is not broken between apis.

References

Spring’s official page on Spring Cloud Contract

An Intro to Spring Cloud Contract

Software professional with a passion for new technologies