Contract testing with Spring Cloud Contract (Part 2)

In the last part, I have briefly introduced the concept of contract testing and why we need it. This article will look into how we can use the Spring Cloud Contract to enforce contract testing between Spring-boot microservice apps.

If you want to quickly dive into the code, feel free to check out the example source code in Github: https://github.com/alexthered/spring-boot-contract-testing-demo.

In this example, we will implement two services: producer (where the API is implemented) and consumer (where the API is consumed). The goal is to enforce a fixed, well-defined contract between the producer and the consumer. If any breaking code change is introduced in the producer, the contract test should fail instead of going to the production and break the consumer code later (and that'd be very bad).

Setup the producer

Let's add a dummy API endpoint to the producer:

// User.java
@Value
@Builder
public class User {

Integer id;
String firstName;
String lastName;
String email;
}

Now to generate a cloud contract for this API endpoint, we need to add the following dependency:

Note that we can add a base test class IntegrationTestBase, which will later be used by Spring to generate test classes.

Now, let's add a contract definition for this endpoint:

Basically, this file defines the expected response that the consumer can expect when calling our dummy endpoint including the status, header, and the body. Now if we run the build, the test classes will be generated:

That's cool, right? So whenever someone introduces a breaking change to the endpoint, the test here would fail.

Setup the consumer

Let's implement another simple endpoint in the consumer, which actually calls our producer's dummy endpoint.

and we write a test for this consumer's endpoint:

Several not-so-magical things happen here. Firstly in the test, we simply call the consumer's endpoint, but as it depends on the producer, we need to tell Junit what we expect from the producer. And how we can do that? Well, remember that we have the contract defined above right?

This piece of code tells Spring to look for the contract between the consumer and producer to get the correct response when we call the producer's endpoint. In the underlying, Spring runs a stub Wiremock server at the port 8080 and load the contract to determine what to return as the response.

Now, again when a developer changes some code in the producer, which will change the contract (e.g in this case to not return the first name of the user), it will also cause the test here in the consumer to fail. Thus, the team can detect the issue way before it is deployed to production to fail silently.🔥

Spring Cloud Contract's approach is more like a producer-driven contract (the contract is written and generated by the producer). In reality, a consumer-driven contract can be a better approach as only the consumer knows which fields are used and what format it expects. Any change if needed, should be driven by the consumer rather than the producer.

Another disadvantage of Spring Cloud Contract is the limitation to the JVM world. Even though there has been some progress to support non-JVM frameworks and languages, it is still somewhat limited.

If you have used Spring Cloud Contract in production, feel free to leave a comment here and tell us how it works out for your team.

Product engineer / novice writer (alexthered.me)

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store