Consumer-Driven Contract Testing
If you are working in the testing domain, you probably have heard about microservices. Many organizations who have moved into modern DevOps practices are also investing in converting or building new projects into Microservice based architecture. Contract Testing in the Microservice architecture is about splitting the application into small standalone services, each of which plays a separate role in the overall system. Being a new technology, it comes with certain challenges, especially in the testing domain. Testers, especially web testers, are now figuring out new ways of automation testing approach in the world of microservices. Before digging deeper, let’s understand what is microservices.
How Does It Work
There are usually 2 ways of developing an application. The most commonly used type is
Benefits of Monolithic architecture
A Monolithic approach gives a boost a the beginning of the project. There is no need to define complex integration testing and deployment processes. Everything is trivialized to a single service.
- Single codebase — simple to develop (at least in the beginning).
- No need to verify integration with other services since they don’t exist.
- Relatively easy deployment process — only one service needs to be deployed.
- Single point of failure — if one app is down, the whole service is down.
- Maintenance complexity grows with time.
- Hard to adopt new technologies and techniques. Usually, it requires rewriting the application from scratch.
Benefits of Microservices
Microservices have many advantages over monolithic architecture (where a single application takes responsibility for the whole system). The most important ones are:
- No single point of failure. When one service is down users can still use the application. Other services are still working.
- Individual Microservices can be scaled up to increase their capacity and availability.
- Security — most of the services aren’t exposed to the Internet.
- Every service can be deployed independently of other services. Providing they don’t break the API contracts.
- Lots of work need to be invested to set up the foundation.
- Deployment processes are more complex. Some deployments may require deploying more than one service.
- Tons of work on DevOps. Especially with deployments.
- Dealing with integration testing.
Most large applications are developed by hundreds of developers. That’s why splitting an application into smaller services makes work easier and faster. The Microservices architecture is a good choice for medium and large applications, although the most successful startups began their applications in monolithic architecture, and migrated to Microservices later on.
Before testing a microservice application, a developer needs to perform the following strategies
- Ensure the code branch is correct.
- Pull the latest code from the repository.
- Ensure dependencies are all updated.
- Rerun if there is any database migration.
- Start the service.
Designing Microservices For Some Application
Let’s consider we have different services in an application like Website, Android App, iPhone App, API Gateway, User Service, Job Service, Payment Service, Email Service. The website and mobile apps communicate with services through the API Gateway. The API Gateway is the only way to access the services from the Internet. The services communicate with each other inside the internal network.
Imagine a user creates a new account. They enter the website, fill in the form, and send the request to the API Gateway. The API Gateway delivers this request to the User Service that creates a new account. After creating an account, User Service sends a request to the E-Mail Service to send an activation e-mail. An e-mail is then sent with a link that the user needs to click to activate their account.
As you noticed, the E-mail service isn’t available over the API Gateway. This is because only internal services can trigger e-mail notifications.
Consumer-Driven Contract Testing
Consumer-driven contract tests are actually integration tests that are targetting your API, whether it’s REST-based or messaging-based. Imagine you’re working on an application that exposes its data using a REST API. Another team is using your exposed data for some functionality that they are providing. In order to guarantee that the functionality of the other team their application doesn’t break if we make changes to our API, we create a contract between the two teams. Key in this setup is that the contracts are defined by the consumer of your API instead of the developer that wrote the implementation of certain functionality. With this approach, we can generate tests by using those consumer-driven contracts, and verify whether
we’re going to break any of our consumers’ applications.
Instead of running integration tests between all the services — get rid of them. All services communicate through RESTful APIs. That means that if we define a tight “contract” between APIs we don’t need to spin up the whole platform. It is enough to verify the fulfillment of the requirements of
To summarize, contract testing means that we verify our API against a set of expectations (contracts). This means that we want to check if upon receiving a specific call, our API provider server will return the data we specified in the documentation. We often lack precise information on the needs of our API consumers. To overcome this problem consumers can define their expectations as mocks that they use in unit tests, creating contracts that they expect us to fulfill. We can gather these mocks and verify that our provider returns the same or similar data when called the same way as the mock is set up, essentially testing the service boundary. This approach is called consumer-driven contract testing.
A bit of software that does something specific is sometimes called a service provider. Something that needs that service is a service consumer. If a client wants data from an API the API is the provider and the client is the consumer. This communication is done under a contract – a set of
specifications by which the consumer uses a provider. CDC testing is where you write the tests from the consumer’s point of view.
What is the 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 pact creates json files containing information on HTTP requests.
What is a Provider?
- A provider is a service or server that provides the data (for example, an API on a server that provides the data the client needs, or the service that sends messages).
- A tool for verifying contracts towards provider is called Pact Provider Verifier. The verifier runs HTTP requests base on the contracts created by the consumer.
- If the server response is in the form expected by the consumer, the tests pass.
How to Deliver Contracts to all Peers?
After all tests on the consumer side succeed, the json files containing contracts are created. Our job is to deliver them to the providers to verify the contract. There are several ways of doing this:
- Git repository for storing pacts, and including them into each project with a git submodule. In my opinion the best way of doing it,
- A file system.
- The Pact Broker.
The Pact Broker is an application for sharing for consumer-driven contracts and verification results. We can push our contracts there and allow the service providers to download them, and run tests against them.
DiUS provides a cloud version of the Pact Broker so, maybe you can take a look and use it. I don’t see too many advantages in running your own Pact Broker. In the end, it is yet another service which requires maintenance from DevOps. If you want to run by yourself here is the Docker images.
I think storing contracts in a separate git repository is good enough. Personally, I would create them during the integration pipeline, and run tests on the service providers within the same integration job— example integration pipeline here.
Why Should we do Contract Testing?
Usually, we want to move quickly with fast iterations, which means that we can try out ideas quickly and dispose of the ones that don’t work – so we don’t get stuck with bad decisions when it turns out there’s a better one.
However, as architectures grow, it can be difficult to figure out what broke what – especially when our service has multiple consumers. We can write integration tests to make sure that the service boundaries are safe, but those tend to be difficult and slow.
the way is to write contract tests, which help us to make sure that we
“ Another fulfill the contract that we provide to our consumers.
But what if a change has to be rolled out quickly, and we forget about contract testing?
We have a lot of responsibilities when we introduce changes: we have to make sure the new version does not introduce a breaking change, or if it does, we have to create a new version of the endpoint, document the updated API, write unit tests, write integration tests, and so on.
If we do not control all the consumers of our APIs, the exact needs of our consumers can get lost in translation. Even if our integration tests catch the problem, we might not know whether we caught a bug in the consumer, or we did not fulfill our contracts properly.
The good news is that our consumers surely have unit tests in place. These tests should run in isolation so all dependencies are ought to be mocked, including our API provider. These mocks essentially specify a contract they expect us to fulfill. Couldn’t we use those to make sure our API
serves the data they need?
Yes, we definitely can! That is called consumer-driven contract testing.
When it comes to contract testing, Pact is the go-to tool these days. We can use it for mocking on the client side, and for sharing these mocks with the API providers. This way, the API providers can check if the changes they introduce would break anything downstream.
Let’s take a look at implementing such a solution!
A Holistic Strategy to Contract Testing
Contract Testing in the digital world helps companies to develop and deliver software services which further help to serve the clients efficiently. To know more about Contract Testing, we advise taking the following steps –