Spring Cloud Contract testing – consumer driven

In the following article we are going to integrate spring cloud contract testing into a new microservice (producer) and we will create another microservice that will act as a consumer.

Producer service will push stubs artifact to local maven or remote artifactory and consumer service will read the stub and start a stubbing server using wiremock. A rest client will then call the stubbed contract and validate the fields.
  1. Producer service configuration

We will use bonus-service and configure it to create the contract stubs and and automatically generated producer test that verifies our API contract.

Service has no contract testing integration but in the following steps we will :

  • Add contract testing dependency and maven contract plugin
  • Create base test class for autogenerated tests
  • Create contract for one of our endpoints

Adding the dependencies in the pom file

a. spring-cloud-starter-contract-verifier
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-contract-verifier</artifactId>
<version>2.2.4.RELEASE</version>
<scope>test</scope>
</dependency>
b. Maven plugin
<plugin>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-contract-maven-plugin</artifactId>
 <version>2.1.1.RELEASE</version>
    <extensions>true</extensions>
    <configuration>
      <baseClassForTests>
        com.bonus.service.contract.BaseTestClass
      </baseClassForTests>
    </configuration>
</plugin>

NOTE: Select dependency according to your spring boot version

Create BaseTestClass for your controller

The location of the class needs to be according to what we set in the maven plugin area : com.bonus.service.contract.BaseTestClas

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@DirtiesContext
@AutoConfigureMessageVerifier
public class BaseTestClass {

    @Autowired
    BonusController bonusController;

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

Create contract for one of our endpoints

When running the bonus-service application we can reach swagger-ui by using our localhost

http://localhost:8080/swagger-ui.htm

Using getAllBonuses endpoint will give us a list of bonuses available (use addBonus to create one before using getAllBonuses, use startTime and endTime according to contract bellow)

Contract location: src/test/resources/contracts/shouldGetBonus.groovy

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

Contract.make {
    description("Should return a bonus")
    request {
        method GET()
        url("/bonus/getAllBonuses")
    }
    response {
     body(
[
        "id"       : $(c(1), p(anyPositiveInt())),
        "bonusName": $(c("You got an iPhone"), p(anyNonEmptyString())),
        "bonusType": $(c("Reward Bonus"), p(anyNonEmptyString())),
        "startTime": $(c("2020-10-10"), p(anyNonEmptyString())),
        "endTime"  : $(c("2023-10-10"), p(anyNonEmptyString())),
      ]
)
        status(200)
    }
}

Running mvn clean install on the bonus-service project will generate the stubs and automatically create a test class that checks the contract fields

2. Consumer service configuration

We will create our consumer service using https://start.spring.io/ and open the project into IDE

The dependency that allows us to create the consumer test on the contract is

<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-contract-stub-runner</artifactId>
 <scope>test</scope>
dependency>

Now let’s create the spring cloud contract consumer test

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
@AutoConfigureStubRunner(
	        stubsMode = StubRunnerProperties.StubsMode.LOCAL,
		ids = "com.bonus.service:bonus:+:stubs:8090")
class BonusClientConsumerApplicationTests {

	RestTemplate restTemplate = new RestTemplate();

	@Test
	void getAllBonuses() throws Exception {
		ResponseEntity<String> response = restTemplate.getForEntity("http://localhost:8090/bonus/getAllBonuses", String.class);
	Assertions.assertEquals(response.getStatusCode().is2xxSuccessful(), true);
	Assertions.assertEquals(response.getBody().contains("Super iPhone"), true);
	}
}

Now let’s break them and explain stubrunner properties

  • StubRunnerProperties.StubsMode.LOCAL

Setting the StubMode to LOCAL means that the stubrunner will search for stubs located into our LOCAL M2 maven repository according to the ids property

  • ids = “com.bonus.service:bonus:+:stubs:8090”

Inside of the M2 repository the stubrunner will search for path com/bonus/service/bonus and use the latest version (+), import the stubs into wiremock and start the server on port 8090

Now let’s run the test and see what happens

In the console we can see the following important logs

Use stubs from local repository

Will download stubs and contracts via Aether
Remote repos not passed but the switch to work offline was set. Stubs will be used from your local Maven repository.
Desired version is [+] - will try to resolve the latest version
Resolved version is [0.0.1-SNAPSHOT]

Add into wiremock server /ping and /health and our contract stub

127.0.0.1 - POST /mappings

Host: []
Content-Length: [423]
Content-Type: [text/plain; charset=UTF-8]
Connection: [keep-alive]
User-Agent: [Apache-HttpClient/5.1.3 (Java/11.0.15)]
{
  "id" : "65b1a352-f0ca-4b70-bdaf-79fc9ea54889",
  "request" : {
    "url" : "/bonus/getAllBonuses",
    "method" : "GET"
  },
  "response" : {
    "status" : 200,
    "body" : "{\"bonusName\":\"Super iPhone\",\"startTime\":\"2020-10-10\",\"id\":1,\"endTime\":\"2023-10-10\",\"bonusType\":\"Super Bonus\"}",
    "transformers" : [ "response-template" ]
  },
  "uuid" : "65b1a352-f0ca-4b70-bdaf-79fc9ea54889"
}

Start the stub server using our contract

Started stub server for project [com.bonus.service:bonus:0.0.1-SNAPSHOT:stubs] on port 8090 with [1] mappings

Execute GET request from our test on the endpoint

WireMock : Request received:
127.0.0.1 - GET /bonus/getAllBonuses

Accept: [text/plain, application/json, application/*+json, */*]
User-Agent: [Java/11.0.15]
Host: [localhost:8090]
Connection: [keep-alive]
Matched response definition:
{
  "status" : 200,
  "body" : "{\"bonusName\":\"Super iPhone\",\"startTime\":\"2020-10-10\",\"id\":1,\"endTime\":\"2023-10-10\",\"bonusType\":\"Super Bonus\"}",
  "transformers" : [ "response-template" ]
}
Response:
HTTP/1.1 200
Matched-Stub-Id: [65b1a352-f0ca-4b70-bdaf-79fc9ea54889]

A few improvements can be done to the consumer and producer services like defining dynamic ports for stubrunner and wiremock server , create a application.properties where to configure test configuration and setup REMOTE stub download for consumer but the article is presenting how easy is to integrate contract testing into Java services with spring cloud contract dependencies.

Keep learning and have fun doing it 🙂

One response to “Spring Cloud Contract testing – consumer driven”

  1. […] Create a contract testing framework with Pact or with spring cloud contract testing […]

    Like

Leave a comment

I’m Razvan

Welcome to my technology blog with DevOps and Automation related articles.

Have fun and learn something along the way.

About me

Let’s connect