Non-HTTP Contract Test (Message Pact)

Reading Time: 4 minutes

This blog shows how to create a contract between producer and consumer using the Message Pact framework.

What is Contract Testing?

  • Contract testing is writing tests to make sure the services can communicate well with each other. There are two perspectives in Contract testing: One is the consumer entity using the service and other one is the provider entity that provides the service. As an example, these two parties can be a Frontend and Backend, or two Backend services integrating with each other.

Message Pact Specification

  • In contrast to the request-response interactions, a message interaction only contains a single JSON object representing the message.
  • The provider initiates the interaction by publishing a message. All consumers will read and process the message accordingly. The Pact specification is independent of the transmission medium.

When would I use contract testing?

  • We often use contract testing to test an integration point between multiple services. They are used in APIs, microservices and so on. To know more, we need to understand some basic concepts.
    • Consumer: This service depends on a producer to fulfil their task.
    • Producer: It supplies relevant data to the consumer helping consumer fulfill their task.
  • Contract testing is immediately applicable anywhere where you have two services that need to communicate such as an API client and a web front-end. Although a single client and a single service is a common use case, contract testing really shines in an environment with many services.

Consumer and Producer Architecture

JSON Maven dependency

 <!-- it use for message-pact-provider -->
        <dependency>
            <groupId>au.com.dius</groupId>
            <artifactId>pact-jvm-consumer-junit</artifactId>
            <version>${pact.version}</version>
        </dependency>

 <!-- this dependency use for set the apache kafka-->
        <dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-clients</artifactId>
            <version>2.7.0</version>
        </dependency>

 <dependency>
            <groupId>au.com.dius.pact.consumer</groupId>
            <artifactId>junit5</artifactId>
            <version>4.3.0-beta.6</version>
            <scope>test</scope>
        </dependency>

Testing the Message Consumer

  • Consumer-driven contract testing is a type of contract testing that ensures that a provider is compatible with the expectations that the consumer has of it.
  • Firstly we use the docker-compose file where we saved the Kafka, zookeeper and Kafka-console-consumer, postgres and Pact-broker service images.
version: "3.2"
services:
  zookeeper:
    image: confluentinc/cp-zookeeper:5.5.1
    hostname: zookeeper
    container_name: zookeeper1
    ports:
      - "2181:2181"
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000
  kafka:
    container_name: kafka1
    hostname: kafka
    image: confluentinc/cp-enterprise-kafka:5.5.1
    ports:
      - 9092:9092
      - 29092:29092
    expose:
      - 9092
      - 29092
    depends_on:
      - zookeeper
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_JMX_PORT: 9991
      KAFKA_ZOOKEEPER_CONNECT: 'zookeeper:2181'
      KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,PLAINTEXT_HOST://localhost:29092
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_CONFLUENT_LICENSE_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0
      KAFKA_CREATE_TOPICS: "quickstart-events"

  kafka-setup:
    image: "confluentinc/cp-kafka:5.5.1"
    hostname: kafka-setup
    container_name: kafka-setup
    depends_on:
      - zookeeper
      - kafka
    command: "bash -c 'echo Waiting for Kafka to be ready... && \
                           kafka-console-consumer --topic quickstart-events --from-beginning --bootstrap-server kafka:9092 && \
                           sleep 20 '"

  postgres:
    image: postgres
    healthcheck:
      test: psql postgres --command "select 1" -U postgres
    ports:
      - "5432:5432"
    volumes:
      - postgres-volume:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
      POSTGRES_DB: postgres

  pact-broker:
    image: pactfoundation/pact-broker:2.74.1.0
    ports:
      - "9292:9292"
    depends_on:
      - postgres
    environment:
      PACT_BROKER_PORT: '9292'
      PACT_BROKER_DATABASE_URL: "postgres://postgres:password@postgres/postgres"
      PACT_BROKER_LOG_LEVEL: INFO
      PACT_BROKER_SQL_LOG_LEVEL: DEBUG


volumes:
  postgres-volume:

  • Use the below command to run the docker-compose file.
docker-compose up

Contract (PACT)

  • Read Data From JSON File using object Mapper so we will generate the PACT with dynamic json data.
private static Map<String, Object> jsonMap = new HashMap<>();
    String file = "src/test/java/testcase/event/EmployeeCreatedEvent_2bb56706-fcdd-420e-a707-66507f5f3bd9.json";

@Pact(provider = "Producer", consumer = "Consumer")
    public MessagePact userCreatedMessagePact(MessagePactBuilder builder) throws IOException {
        PactDslJsonBody body = new PactDslJsonBody();
        ObjectMapper objectMapper = new ObjectMapper();
        jsonMap = objectMapper.readValue(new File(file),
                new TypeReference<Map<String, Object>>() {
                });
        for (Map.Entry<String, Object> entry : jsonMap.entrySet()) {
            body.stringValue(entry.getKey(), entry.getValue().toString());
        }

        MessagePact a_user_created_message = builder
                .expectsToReceive("a user created message")
                .withContent(body)
                .toPact();

        return a_user_created_message;
    }
  • Use @PactVerification(userCreatedMessagePact) annotation so after using this annotation we can verify the generated Contract.
 @Test
    @PactVerification("userCreatedMessagePact")
    public void verifyCreatedPact() throws IOException, InterruptedException {
        assertEquals("432cc207-40d4-469f-b062-97841dfc7197", jsonMap.get("id"));
    }
  • Add the @PactFolder(//location of generated pact) annotation in upper the class so their generated pact will be saved.
  • Add the below URL in the Pact-provider plugin in the POM file so pact-broker is going to running state.
<pactBrokerUrl>http://localhost:9292</pactBrokerUrl>
  • Use the below code to send the event to the consumer from the producer on a particular Kafka topic.
 public void ProduceEvent(String filename) throws IOException {
        Stream<String> objec = Files.lines(Paths.get(filename));

        Properties properties = new Properties();
        properties.put("bootstrap.servers", "localhost:29092");
        properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");

        KafkaProducer<String,String> sampleProducer= new KafkaProducer<String,String>(properties);
        objec.forEach(f->{
            ProducerRecord<String, String> record = new ProducerRecord<String, String>("quickstart-events",f);
            sampleProducer.send(record);
        });
        sampleProducer.close();

    }
    @Test
    public void Test () throws IOException {
        ProduceEvent("Event.json.json");
    }

Generated Pact

{
  "consumer": {
    "name": "consumer"
  },
  "provider": {
    "name": "producer"
  },
  "messages": [
    {
      "description": "a user created message",
      "metaData": {
        "contentType": "application/json"
      },
      "contents": {
        "sequenceNumber": "0",
        "specVersion": "2.9",
        "schemaVersion": "3.1",
        "dataCenter": "DLAS1",
        "effectiveDatetime": "2021-09-08T19:00:00Z",
        "type": "opp-ds.employment.created"
      }
    }
  ],
  "metadata": {
    "pactSpecification": {
      "version": "3.0.0"
    },
    "pact-jvm": {
      "version": "4.0.10"
    }
  }
}
  • Use the below command to publish the PACT into Pact – broker.
mvn pact:publish 

Testing the Message Producer

  • As compared to the consumer driven-contract testing a producer-driven contract testing is rarely used.
  • In this testing, a producer takes charge of creating a contract between them and the consumer. 
  • To implement provider-driven contract testing, we need to have the same principles: The contract is only published when it is validated against the providers’ service.
  • The consumer verifies their code against the published contract and then publishes the results of the verification.

Techhub Template Reference

https://github.com/knoldus/Message-contract-test-with-dynamic-data

Referenece

https://docs.pact.io/


Knoldus-blog-footer-image

Written by 

Prajjawal is a QA Consultant having experience of more than 1 year. He is familiar with core concepts of manual & automation testing using tools like Selenium and Postman Also having knowledge of Python and Data Science. He is always eager to learn new and advanced concepts in order to improve himself. He likes to watch web series and play cricket.