Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

REST Contract Testing — Pact Gen

DZone 's Guide to

REST Contract Testing — Pact Gen

Learn more about Pact Gen and see an analysis of mapping from a sample Feign client taken from a Judge-d agent to a Pact file.

· Integration Zone ·
Free Resource

Image title

Source

Judge-d, an open source framework for performing contract testing, in case of validation of REST contracts, requires that consumer expectations are expressed using Pact files. Pact files are a result of integration tests written for each interaction between the consumer and the provider of the service. Writing and maintaining such tests might be troublesome. Therefore, Pact Gen, an open source tool used to automatically generate Pact files, was created.

Motivation

Imagine that you are assigned a task of introducing contract testing into your ecosystem, and you decided to use Judge-d. Let’s assume JMS is used in the systems to provide asynchronous communication between services, and REST is used to provide synchronous communication between services.

JMS is nicely handled by Vaunt, an open source framework for definition and validation of JMS contracts, which is supported by Judge-d.

In the case of REST contracts, Judge-d expects capabilities of a provider to be expressed using a Swagger file and expectations of a consumer using Pact files. Creation of Swagger files can be easily automated, whereas Pact files are generated based on integration tests for each possible interaction between the consumer and the provider.

This approach has certain drawbacks. Firstly, the Pact framework treats these integration tests as a good way of validating interactions on the consumer side. Conversely, in the case of Judge-d, these tests are redundant. Judge-d takes ownership of contract validation no matter what was changed — a consumer or a provider (for details about how Judge-d achieves that, please refer to this article.). Therefore, it is a good idea to automate the process of Pact files generation. It is the main goal of the Pact Gen open source project.

General Idea

Automatic generation of Pact files using Pact Gen includes 2 phases – representation extraction and file generation.

Representation extraction is a step whereby REST clients are found in the project, analyzed and all available data about them is used to create their representation. It is possible to provide more details about the clients using annotations introduced by Pact Gen, which are described on the GitHub page of the project. Pact Gen is designed to be extendable, but for now, it supports Feign clients (declarative REST clients), but adding support for other technologies is possible as well.

File generation is the second step in the process of Pact file generation. Its main goal is to use the representation of REST clients generated in the first step and create Pact files, which contain a description of each interaction with the providers. Each interaction is described using random values of parameters conforming to the constraints from the REST client representation. Random values are generated using the PODAM library.

Feign Client to Pact File Mapping

In order to better understand how Pact Gen works, an analysis of mapping from a sample Feign client taken from a Judge-d agent to a Pact file is useful.

@FeignClient("judge-d-server")
public interface JudgeDPublisher {

    @PutMapping(path = "environments/{environment}", produces = "application/json")
    void publish(@PathVariable("environment") String environment, @RequestBody Set<ServiceForm> serviceForms);

    @Data 
    class ServiceForm {
        private String name;
        private String version;
    }
}

Feign client used by Judge-d agent

{
  "provider": {
    "name": "judge-d-server"
  },
  "consumer": {
    "name": "judge-d-agent"
  },
  "interactions": [
    {
      "description": "publish request; 200 OK response",
      "request": {
        "method": "PUT",
        "path": "environments/405TNpbKEX",
        "headers": {},
        "query": "",
        "body": [
          {
            "name": "naEnT3jcAR",
            "version": "C3HpDYU_Xh"
          }
        ]
      },
      "response": {
        "status": "200 OK",
        "headers": {}
      }
    }
  ],
  "metadata": {
    "pactSpecificationVersion": "1.0.0"
  }
}

Generated Pact file

In the first code block, the Feign client used by the Judge-d agent is presented. In the second code block, the Pact file representing interaction from the consumer perspective is shown. In the table below, sample mapping from Feign client to Pact file done by Pact Gen is described.

Feign client Pact file

@FeignClient("judge-d-server")

"provider": {

"name": "judge-d-server"

},

provided in a test running Pact Gen

"consumer": {

"name": "judge-d-agent"

},

default

"description": "publish request; 200 OK response",

@PutMapping

"method": "PUT",

path = "environments/{environment}",


@PathVariable("environment") String environment

"path": "environments/405TNpbKEX",

@RequestBody Set<ServiceForm> serviceForms


class ServiceForm {

private String name;

private String version;

}

"body": [

{
"name": "naEnT3jcAR",
"version": "C3HpDYU_Xh"
}

]

default

"status": "200 OK",


Feign client to Pact file mapping

In the first row, the source of the provider name is shown. It is taken from an element of “@FeignClient” annotation from Feign framework.

In the second row, the source of the consumer name is shown. It is provided in a test, which needs to be written to run Pact Gen. This test is described in subsequent sections.

In the third row, the source of the interaction description is shown. It is generated using the name of the method in Feign client representing the interaction. The default HTTP response status is “200 OK”, and such information is provided in the description as well.

In the fourth row, the source of the HTTP method name is shown. It is taken from “PutMapping” annotation provided by Spring.

In the fifth row, the source of the path parameter is shown. It is taken from “path” element of “PutMapping” annotation. The type of element is taken from an element of “PathVariable” annotation provided by Spring. This type is used by Pact Gen to generate random value conforming to specified constraints (if any).

In the sixth row, the source of the request body is shown. It is taken from the parameter of the method representing the interaction and being annotated with the “RequestBody” annotation from Spring. Types of body fields are taken from the class representing the parameter. Random values conforming to the constraints (if any) are generated by Pact Gen and later used in the target Pact file.

In the seventh row, the source of the HTTP response status is shown. It has a default value “200 OK”, but it can be customized.

Details of the various possibilities of customization of Pact Gen behavior and rules on how Pact files are generated by Pact Gen can be found on the GitHub page of the project.

Sample Usage

After a general overview of what Pact Gen offers, it is helpful to go through a step-by-step description of how to use it, especially in combination with Judge-d.

class ContractTestsGenerator extends Specification {

    @Autowired
    ObjectMapper objectMapper

    PactGenerator pactGenerator = new PactGenerator()

    def "should generate pact file"() {

        expect:
            pactGenerator.writePactFiles(
               "pack.age", "service-name", objectMapper, new File("build/pacts/"))
    }
}

Integration test to launch Pact Gen

The first step is the creation of a simple test, which responsibility is to launch Pact Gen. The test is shown in the code block above. A new instance of Pact Gen must be created, and its method, “writePactFiles”, launched. It has 4 parameters. The first one is a package, which needs to be scanned in search of Feign clients i.e. any interface annotated with “FeignClient” annotation from “org.springframework.cloud.openfeign”.

The second parameter is the name of the service. This name is later used as a service consumer name.

The third parameter is a reference to ObjectMapper from Fasterxml, which is used to serialize and deserialize objects to and from JSON in this application.

The fourth parameter is a file where a newly-generated Pact file must be saved.

That is all that is required in order to use Pact Gen. However, running this test now would mean that no customization is used for found Feign clients. For example, default response status would be used, no response headers would be specified, etc. In order to customize the description of interactions, custom annotations should be used.

Roadmap

Pact Gen, for now, supports the creation of Pact files basing on Feign clients. However, in the roadmap of the project, support for other technologies like JAX-RS is included.

Topics:
microservices architecture ,contract testing ,rest ,integration

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}