1. Overview
In this quick article, we’ll be looking at the concept of Consumer-Driven Contracts.
We will be testing integration with some external REST service through a contract using the Pact library. That contract can be defined by the external service and shared with every client that needs to call it. We’ll create a test based on the contract in the client application.
2. What is Pact?
Using Pact, we can define consumer expectations for a given provider (that can be an HTTP REST service) in the form of a contract (hence the name of the library).
We’re going to set up this contract using the DSL provided by Pact. Once defined, we can test interactions between consumers and the provider using the mock service that is created based on the defined contract.
3. Maven Dependency
To get started we’ll need to add Maven dependency to pact-jvm-consumer-junit_2.11 library:
<dependency> <groupId>au.com.dius</groupId> <artifactId>pact-jvm-consumer-junit_2.11</artifactId> <version>3.5.0</version> <scope>test</scope> </dependency>
4. Defining a Contract
When we want to create a test using Pact, first we need to define a @Rule that will be used in our test:
@Rule public PactProviderRuleMk2 mockProvider = new PactProviderRuleMk2("test_provider", "localhost", 8080, this);
We’re passing the provider name, host, and port on which the server mock (which is created from the contract) will be started.
Let’s say that service has defined the contract for two HTTP methods that it can handle.
The first method is a GET request that returns JSON with two fields. When the request succeeds, it returns a 200 HTTP response code and the content-type header for JSON.
Let’s define such a contract using Pact.
We need to use the @Pact annotation and pass the consumer name for which the contract is defined. Inside of the annotated method, we can define our GET contract:
@Pact(consumer = "test_consumer") public RequestResponsePact createPact(PactDslWithProvider builder) { Map<String, String> headers = new HashMap<>(); headers.put("Content-Type", "application/json"); return builder .given("test GET") .uponReceiving("GET REQUEST") .path("/") .method("GET") .willRespondWith() .status(200) .headers(headers) .body("{\"condition\": true, \"name\": \"tom\"}") (...) }
Using the Pact DSL we define that for a given GET request we want to return a 200 response with specific headers and body.
The second part of our contract is the POST method. When the client sends a POST request to the path /create with a proper JSON body it returns a 201 HTTP response code and the proper header.
Let’s define such contract with Pact:
(...) .given("test POST") .uponReceiving("POST REQUEST") .method("POST") .headers(headers) .body("{\"name\": \"Michael\"}") .path("/create") .willRespondWith() .status(201) .headers(headers) .body("") .toPact();
Note that we need to call the toPact() method at the end of the contract to return an instance of RequestResponsePact.
5. Testing Interactions Using the Defined Contract
Once we defined the contract we can test interactions with the service that will be created out of that contract. We can create normal JUnit test but we need to remember to put the @PactVerification annotation at the beginning of the test.
Let’s write a test for the GET request:
@Test @PactVerification() public void givenGet_whenSendRequest_shouldReturn200WithProperHeaderAndBody() { // when ResponseEntity<String> response = new RestTemplate() .getForEntity(mockProvider.getUrl(), String.class); // then assertThat(response.getStatusCode().value()).isEqualTo(200); assertThat(response.getHeaders().get("Content-Type").contains("application/json")).isTrue(); assertThat(response.getBody()).contains("condition", "true", "name", "tom"); }
The @PactVerification annotation takes care of starting the HTTP service. In the test, we only need to send the GET request and assert that our response complies with the contract.
Let’s add the test for the POST method call as well:
HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.setContentType(MediaType.APPLICATION_JSON); String jsonBody = "{\"name\": \"Michael\"}"; // when ResponseEntity<String> postResponse = new RestTemplate() .exchange( mockProvider.getUrl() + "/create", HttpMethod.POST, new HttpEntity<>(jsonBody, httpHeaders), String.class ); //then assertThat(postResponse.getStatusCode().value()).isEqualTo(201);
As we can see, the response code for the POST request is equal to 201 – exactly as it was defined in the Pact contract.
As we were using the @PactVerification() annotation, the Pact library is starting the web server based on the previously defined contract before our test case.
6. Conclusion
In this quick tutorial, we had a look at Consumer Driven Contracts.
We created a contract using the Pact library. Once we defined the contract, we were able to test interactions with the external service and assert that it complies with the specification.
The implementation of all these examples and code snippets can be found in the GitHub project – this is a Maven project, so it should be easy to import and run as it is.