Here at Orbit Ventures in Hamburg, in one of our customer projects, we have evolved a monolith to a microservice architecture. Among several obstacles we faced in doing so, was how to do integration testing. We ended up introducing “Consumer-Driven Contract Testing” (or in short “CDCT”) by using the framework Pact - which is the predominant way to implement CDCT. This blog post is answering the why, how and what of CDCT and Pact. Namely, it is the introduction that I wished I had when starting out to understand the concept myself and “sell it in” to fellow developers - have fun. 😊
Evolving an architecture from a monolith to having multiple services, e.g. as part of a micro service architecture, complicates integration testing, as functionality can no longer be tested end-to-end by deploying a single service. Specifically, the question to answer is; **how can we at deploy-time know whether the service being deployed, let’s call it “Service A”, works effectively with all other services of the environment being deployed to?** I.e. clients become expected responses from servers.
The “traditional” approach answering the above question is to apply end-to-end testing, i.e. deploying all services of a system in their environments to test integration points, as illustrated below.
End-to-end testing’s main benefit is that it may deliver the guarantee that deployed changes work effectively with other services of a system. However, the guarantee comes at relatively high costs - due to:
So, the natural question is, how to minimise these costs? CDCT to the rescue! Let’s start by defining what it is in the following section.
CDCT is another approach to integration testing and "is a technique for testing an integration point by checking each service in isolation to ensure the messages it sends or receives conform to a shared understanding that is documented in a 'contract' (Added by author: called a “Pact”).
For applications that communicate via HTTP, these "messages" would be the HTTP request and response, and for an application that used queues, this would be the message that goes on the queue.” (pact)
In the realms of CDCT, the terminology is like so:
Each of the elements together with their internal relations can be seen of image below.
Remark, a service can both be a Consumer and Provider at the same time, e.g. a “BFF service” may provide clients with data to display to the user, whereas it may consume messages from a payment service whenever a payment is conducted.
You may also ask, what’s up with the “Consumer-driven” part of the naming CDCT? Within a consumer-driven approach, the pacts are generated during the execution of the consumer tests. As opposed to Provider-driven, where pacts are generated during provider tests. A key advantage over provider-driven is that only parts of the communication that are actually used by consumer(s) get tested.
Having an understanding of what CDCT is, the question becomes, how does CDCT allow testing of each service in isolation? This is what we will look into now using the example of a REST call. Each step of CDCT is also illustrated in the figure below.
Consumer: During execution of consumer tests, the control flow is:
1.1: A Pact “mock Provider” records requests and expected responses, while documenting those in Pacts.
1.2: The Pacts are published to a Pact Broker.
1.3: The Pact Broker is asked whether it is safe to deploy code to the target environment. If safe, the code can be deployed, if not, the consumer team contacts the provider team to work out a solution to fulfil the consumer expectations.
Provider: During execution of a providers tests, the control flow is:
2.1: Pacts relevant to the Provider are retrieved from the Pact Broker.
2.2: A Pact “mock Consumer” requests the “real” provider and compares the actual responses of the “real” provider” with the documented responses of the Pact(s). If they match, it means that the two services will communicate effectively when Interacting.
2.3: The verification results are published to the Pact Broker.
While CDCT, like end-to-end testing, may provide the guarantee that a deployed service works with all other services of a system, it does so at much lower costs, because:
The predominant way around implementing CDCT is to use Pact. It delivers a cohesive framework for implementing CDCT in your project and provides plugins to most of the well known backend technologies. To get started, take a look at the official pact documentation.
I believe it takes some time to fully understand and wrap one's head around CDCT and its uses. Meanwhile, in my experience, this has proven easier when actually implementing and using Pact in a real project. However, implementing Pact is also, relatively speaking, the larger hurdle. It does not help that documentation of Pact is widespread and somewhat lacks a coherent line of thought. Hence, we are considering writing another post detailing the implementation from A till Z of both a consumer, provider and the Pact Broker - please let us know if this may be of interest to you.