Consumer Driven Contract Test Using Spring Cloud Contract

Photo by Beatriz Pérez Moya on Unsplash

This post discusses, What is Consumer Driven Contract test? and How to implement it using Spring Cloud Contract.

The code for this post is available on Github here

Contract Testing

Contract test are set of automated test, That verifies two separate services are adhering to predefine contracts and are compatible with each other.
Aim of contract test is to make sure that, Contract are always kept up to date and each service ( Provider & Consumer) can be tested independently.

Consumer Driven Contract test

In consumer driven contract testing, Consumers are responsible for providing the contract details. In this strategy consumers of API are at the heart of API design process.In consumer driven API design process providers are forced to complete their consumer obligations. Frameworks like pact and Spring Cloud Contracts provides set of tools to implement Consumer Driven Contract test.

Spring Cloud Contract

Spring Cloud Contract provides set of tool for implementing Consumer Driven Contract test for Spring based applications. It has two major component Contract Verifier for Producers & Stub Runner for consumer

Sample application

Let’s write some contract test. Assume we got an requirement from Consumer application Service-A for status API for Provider application Service-B, Which will provide Current Status of user.

In consumer driven contract strategy, as consumer of service, consumers need to define what exactly they want from producer in the form of written contracts. You can provide contract in groovy or yaml format

Provider : Service-B

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.springframework.cloud.contract.spec.Contract
Contract.make {
description "should return user status"

request {
url "/status"
method GET()
}

response {
status OK()
headers {
contentType applicationJson()
}
body (
id: 1,
status: "CREATED"
)
}
}
Implement Contract
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
class UserStatusController(private val userStatusService: UserStatusService) {
@GetMapping("/status")
fun getStatus(): ResponseEntity<UserStatus> {
return ResponseEntity.ok(userStatusService.getUserStatus(1))
}
}

data class UserStatus(val id: Int, val status: String)

@Service
class UserStatusService {

fun getUserStatus(userId:Int):UserStatus{
return UserStatus(1,"ACTIVATED")
}
}

How to verify contracts ?
spring-cloud-starter-contract-verifier helps us to automatically verify the contracts, It’s generates the test cases during the build phase and verify the API response against the contract. Add below dependency in pom

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

To auto-generated tests classes, Add below plugin inside build tag.

1
2
3
4
5
6
7
8
9
10
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>3.0.1</version>
<extensions>true</extensions>
<configuration>
<testFramework>JUNIT5</testFramework>
<baseClassForTests>com.ns.producer.BaseClass</baseClassForTests>
</configuration>
</plugin>

We also need to provide BaseClassForTest, which will be extended by all generated classes. Base class is responsible for providing all needed mocking & spring beans needed for the generated test classes.In out cases this is how base class will look like.

BaseClass
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public abstract class BaseClass {

@Autowired
private UserStatusController userStatusController;
@MockBean
private UserStatusService userStatusService;
@BeforeEach
public void setup() {
Mockito.when(userStatusService.getUserStatus(1)).thenReturn(new UserStatus(1, "CREATED"));
RestAssuredMockMvc.standaloneSetup(userStatusController);
}

}

Now if we run the build, ContractVerifierTest test class will be generated inside /target/generated-test-source/contract generated class will be look like below

Generated Test Class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ContractVerifierTest {
@Test
public void validate_get_status_by_id() throws Exception {
// given:
MockMvcRequestSpecification request = given();
// when:
ResponseOptions response = given().spec(request)
.get("/status");
// then:
assertThat(response.statusCode()).isEqualTo(200);
assertThat(response.header("Content-Type")).matches("application/json.*");
// and:
DocumentContext parsedJson = JsonPath.parse(response.getBody().asString());
assertThatJson(parsedJson).field("['id']").isEqualTo(1);
assertThatJson(parsedJson).field("['status']").isEqualTo("CREATED");
}
}

Consumer : Service-A

On the consumer side , We can use stub generated by Producer application to test the interaction from consumer to producer.To use stub generated by producer add below dependancy in consumer

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

the Unit test to test interaction with Producer will look like below

pom
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@SpringBootTest
class StatusServiceTests {
@Autowired
lateinit var underTest: StatusService

@JvmField
@RegisterExtension
final val stubRunner = StubRunnerExtension()
.downloadStub("com.ns", "producer", "0.0.1-SNAPSHOT", "stubs")
.withPort(8080)
.stubsMode(StubRunnerProperties.StubsMode.LOCAL)

@Test
fun getStatus() {
val status = underTest.getStatus()
assertEquals(status, "CREATED")
}

@Test
fun getPactStatus() {
val status = underTest.getPactStatus()
assertEquals(status, "CREATED")
}
}

The code for this post is available on Github here

Reference Spring Cloud Contract Reference Documentation

Share Comments