Java Consumer Driven Contract Testing

Posted July 3, 2016

Pact-JVM and Gradle in Java

Introduction

In this post, we will write a Consumer Driven Contract Test between a Gradle Spring Boot dummy provider (RESTful Web Service) and a Gradle dummy consumer.

We will make use of Pact-JVM, which will provide a mock service for the consumer project to generate a Pact, and verification ability for the provider project to verify the Pact.

Installation

Gradle

A Gradle Wrapper allows anybody to work on your project without having to install Gradle. It ensures that the right version of Gradle that the build was designed for is shipped as part of this project repository. First clone the sample repository that includes both the dummy provider and the dummy consumer:

bash-3.2$ git clone https://github.com/the-creative-tester/consumer-driven-contract-testing-example.git
Cloning into 'consumer-driven-contract-testing-example'...
remote: Counting objects: 47, done.
remote: Compressing objects: 100% (33/33), done.
remote: Total 47 (delta 2), reused 42 (delta 1), pack-reused 0
Unpacking objects: 100% (47/47), done.
Checking connectivity... done.

Provider Setup

Navigate into the provider directory, where you will see a sample RESTful Web Service that was built using Sprint Boot and Gradle through this guide. Try running the service by using the following command:

bash-3.2$ ./gradlew clean build && java -jar build/libs/gs-actuator-service-0.1.0.jar
..
2016-07-03 14:49:59.367  INFO 32114 --- [           main] hello.HelloWorldConfiguration            : Started HelloWorldConfiguration in 6.782 seconds (JVM running for 7.452)  

You should now be able to reach to service by navigating to http://localhost:8080/hello-world or by using curl (observe that the id increments for every request made to the service):

bash-3.2$ curl http://localhost:8080/hello-world
{"id":2,"content":"Hello World!"}

Consumer Setup

The generation of a Pact is always done from the consumer point of view. This generation can be broken down into three parts.

Part 1: Pact Rule

In this part, we define a mock server’s host and port to represent the provider:

@Rule
public PactRule rule = new PactRule(Configuration.MOCK_HOST, Configuration.MOCK_HOST_PORT, this);
private DslPart helloWorldResults;
Part 2: Pact Fragment

In this part, we construct a Pact Fragment, which defines the expected contract from a provider:

@Pact(state = "HELLO WORLD", provider = Configuration.DUMMY_PROVIDER, consumer = Configuration.DUMMY_CONSUMER)
public PactFragment createFragment(ConsumerPactBuilder.PactDslWithProvider.PactDslWithState builder)
{
    helloWorldResults = new PactDslJsonBody()
            .id()
            .stringType("content")
            .asBody();

    return builder
            .uponReceiving("get hello world response")
            .path("/hello-world")
            .method("GET")
            .willRespondWith()
            .status(200)
            .headers(Configuration.getHeaders())
            .body(helloWorldResults)
            .toFragment();
}
Part 3: Pact Verification

In this part, we ensure that the constructed fragment in Part 2, matches the response from the mock server:

@Test
@PactVerification("HELLO WORLD")
public void shouldGetHelloWorld() throws IOException
{
    DummyConsumer restClient = new DummyConsumer(Configuration.SERVICE_URL);
    assertEquals(helloWorldResults.toString(), restClient.getHelloWorld());
}

Try generating a Pact using Gradle from the consumer directory:

bash-3.2$ ./gradlew test
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava
:processTestResources UP-TO-DATE
:testClasses
:test

BUILD SUCCESSFUL

Total time: 10.67 secs

The Pact will then be generated and stored in the pacts directory, but it looks something like this:

{
  "provider" : {
    "name" : "dummy-provider"
  },
  "consumer" : {
    "name" : "dummy-consumer"
  },
  "interactions" : [ {
    "providerState" : "HELLO WORLD",
    "description" : "get hello world response",
    "request" : {
      "method" : "GET",
      "path" : "/hello-world"
    },
    "response" : {
      "status" : 200,
      "headers" : {
        "Content-Type" : "application/json;charset=UTF-8"
      },
      "body" : {
        "id" : 5677679801,
        "content" : "dugNvVPasiFRnzqpPNuq"
      },
      "responseMatchingRules" : {
        "$.body.id" : {
          "match" : "type"
        },
        "$.body.content" : {
          "match" : "type"
        }
      }
    }
  } ],
  "metadata" : {
    "pact-specification" : {
      "version" : "2.0.0"
    },
    "pact-jvm" : {
      "version" : "2.1.12"
    }
  }
}

Provider

Now that the Pact has been generated by the Consumer, and assuming the Provider (RESTful Web Service) is running. You can run the following command from the provider directory to ensure that the Provider meets all expectations of the Consumer:

bash-3.2$ ./gradlew pactVerify
:pactVerify_dummyProvider

Verifying a pact between dummy-consumer and dummyProvider
  [Using file /Users/jasonthye/Dev/pact-example-gradle/pacts/dummy-consumer-dummy-provider.json]
  Given HELLO WORLD
         WARNING: State Change ignored as there is no stateChange URL
  get hello world response
    returns a response which
      has status code 200 (OK)
      includes headers
        "Content-Type" with value "application/json;charset=UTF-8" (OK)
      has a matching body (OK)
:pactVerify

BUILD SUCCESSFUL

Total time: 9.936 secs

Full Example

https://github.com/the-creative-tester/consumer-driven-contract-testing-example