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
