Pact and its elements in contract testing

Reading Time: 6 minutes

Hi friends,

Welcome to our pact blog series,

This is my first blog on the pact in which we will discuss the basic elements of the pact and know more about contract testing.

What is contract testing?

Contract testing is a technique for testing an integration point by isolating each microservice and checking whether the HTTP requests and responses that the microservice transmits conform to a shared understanding that is documented in a contract.

There are two perspectives in Contract testing:

  • One is the consumer entity using the service,
  • And the other is the provider entity that provides the service. For example, these two parties can be a Frontend and Backend, or two Backend services integrating with each other.
  • When two or more microservices are there for the testing then pact framework is very helpful in that sceriero

Contract testing ensures all the benefits of the integration tests and their advantages.

Because by using integration testing we only get “confidence to release” but by using pact in contract testing we also get some plus points shown below:-

  • give us the confidence to release
  • run independently
  • give us fast feedback
  • are stable
  • are easy to maintain

Pact is an open-source framework that facilitates the testing of components based on contracts. Its main advantages are:

It is a tool for testing HTTP requests, responses, and message integrations by using contract tests. The Pact name is a synonym of the word “contract”.

The pact provides a mechanism for creating a contract between a service consumer and a service provider and then providing the tools to validate that the consumer and provider adhere to the contract independently of each other.

Pacts and its types

Basically, pact are two types,

  • HTTP:

An expected request — describing what the consumer is likely to send to the provider.

A minimal expected response — describes the part of response consumer wants provider to return.

  • Messages:

The minimal expected message describes the parts of the message the consumer wants to use.

Elements of pacts

  • Service Consumer
  • Service Provider
  • Mock Consumer
  • Mock Producer
  • Pact broker

1. Service Consumer:

  • A consumer is a client that wants to receive some data from other services (for example, a web front end, or a message receiving endpoint).
  • They define requirements towards the endpoint such as HTTP headers, status code, payload, and response.
  • The contracts are generated during the unit test runtime.
  • After all, tests succeed it creates json files containing information on HTTP requests.

2. Service producer:

  • A provider is a service or server that provides the data (for example, an API on a server that provides the data with the client needs, or the service that sends messages).
  • A tool for verifying contracts towards provider is called Provider Verifier. The verifier runs HTTP requests base on the contracts created by the consumer.
  • If the server response is in the form expecteContact tracing may sound like taking your contacts out of your eyes and then drawing circles around them. d by the consumer, the tests pass.

3. Mock-Consumer:

  • Mock consumer is a imaginary entity present in producer side and behave as a real consumer for provider which demand some request as service from provider.
  • provider send real response towards its request.

4. Mock-Producer:

  • Mock provider is also a imaginary entity present in customer side and behave as a real provider for consumer.Because it accept the real request and respond back to consumer as expected response
  • It provides a mock HTTP server so you don’t have to depend on the provider.

5. Pact Broker:

image3
  • Pact Broker is a repository for contracts, allowing you to share pacts between consumers and providers.
  • It also contain version of pact contract files so the provider can verify itself against a fixed version of a contract.
  • It provide documentation for each pact as well as a visualization of the relationship between services.
  • Basically it a medium in which consumer upload the generated json file and then provider take this pact and verifed and return to this broker.

Journey of pact:-

In contract testing with pact covered many steps as shown below;-

  • run our code in IntelliJ IDE
  • after succesfully execution a json file is generated on location specfiled build.gradle file in intelliJ as,
pact {
	publish {
        pactDirectory = "target/pacts"
		pactBrokerUrl = 'http://localhost:9292/'
	}
  • Then consumer upload this json file into pact broker
  • now this state contain a pact.
  • now provider take the json file from pact broker.
  • after that provider run and verifiy the contract between consumer and provider.
  • In last step provider upload the our responce where it passed or failed.

3. How we define pact

3.1 Pact Definition

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 provider = new PactProviderRuleMk2(provider:"user-service", hostInterface: null,randomPort.getPort(), target:this);

Let’s define such a contract using a @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:

We’re passing the provider name, host, and port on which the server mock (created from the contract) will be started.

 @Pact(consumer = "messaging-app", provider = "user-service")
    public RequestResponsePact pactUserExists(PactDslWithProvider builder) {

       
        DslPart body = LambdaDsl.newJsonBody((o) -> o
            .stringType("id", "1")
            .stringType("name", name)
            .timestamp("lastLogin", "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
                Date.from(LAST_LOGIN.atZone(ZoneId.systemDefault()).toInstant()))
            .stringMatcher("role", "ADMIN|USER", "ADMIN")
            .minArrayLike("friends", 0, 2, friend -> friend
                .stringType("id", "2")
                .stringType("name", "a friend")
            )).build();

        return builder.given("default", Collections.singletonMap("userExists", true))
            .uponReceiving("A request for an existing user")
            .path("/users/1")
            .method("GET")
            .willRespondWith()
            .status(200)
            .body(body)
            .toPact();

    }

when we complete the pact with its verification and after execution, we get a JSON file that contains a body

3.2 Generated json file

the build will generate a file called test_consumer-test_provider.json in the target/mypacts folder, which contains the structure of the requests and responses:

{
    "provider": {
        "name": "user-service"
    },
    "consumer": {
        "name": "messaging-app"
    },
    "interactions": [
        {
            "description": "A request to /users/1",
            "request": {
                "method": "GET",
                "path": "/users/1"
            },
            "response": {
                "status": 200,
                "headers": {
                    "Content-Type": "application/json; charset=UTF-8"
                },
                "body": {
                    "friends": [
                        {
                            "id": "2",
                            "name": "a friend"
                        },
                        {
                            "id": "2",
                            "name": "a friend"
                        }
                    ],
                    "lastLogin": "2021-11-16T12:34:12.000Z",
                    "name": "user name for CDC",
                    "role": "ADMIN"
                },
                "matchingRules": {
                    "body": {
                        "$.name": {
                            "matchers": [
                                {
                                    "match": "type"
                                }
                            ],
                            "combine": "AND"
                        },
                        "$.lastLogin": {
                            "matchers": [
                                {
                                    "match": "timestamp",
                                    "timestamp": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
                                }
                            ],
                            "combine": "AND"
                        },
                        "$.role": {
                            "matchers": [
                                {
                                    "match": "regex",
                                    "regex": "ADMIN|USER"
                                }
                            ],
                            "combine": "AND"
                        },
                        "$.friends": {
                            "matchers": [
                                {
                                    "match": "type",
                                    "min": 0
                                }
                            ],
                            "combine": "AND"
                        },
                        "$.friends[*].id": {
                            "matchers": [
                                {
                                    "match": "type"
                                }
                            ],
                            "combine": "AND"
                        },
                        "$.friends[*].name": {
                            "matchers": [
                                {
                                    "match": "type"
                                }
                            ],
                            "combine": "AND"
                        }
                    },
                    "header": {
                        "Content-Type": {
                            "matchers": [
                                {
                                    "match": "regex",
                                    "regex": "application/json;\\s?charset=(utf|UTF)-8"
                                }
                            ],
                            "combine": "AND"
                        }
                    }
                }
            },
            "providerStates": [
                {
                    "name": "User 1 exists"
                }
            ]
        },
        {
            "description": "A request to /users/2",
            "request": {
                "method": "GET",
                "path": "/users/2"
            },
            "response": {
                "status": 404
            },
            "providerStates": [
                {
                    "name": "User 2 does not exist"
                }
            ]
        }
    ],
    "metadata": {
        "pactSpecification": {
            "version": "3.0.0"
        },
        "pact-jvm": {
            "version": "3.5.24"
        }
    }
}

That’s all for this blog, I Hope so learned about the pact and its related term contract testing, and in the next blog, we will learn more about it, so stay tuned.

Thank you!!  

References

https://docs.pact.io/

https://laptrinhx.com/how-to-test-java-microservices-with-pact-476844305/

knoldus footer

Written by 

I am a software intern in knoldus software limited, I have completed my graduation from vidya college of engineering in 2021.

Leave a Reply