Docker & Wiremock isolation pattern
By: Luca Pelosi
Tags: Software Engineering, Testing Tools, Docker, Wiremock, Testing

Docker & Wiremock isolation pattern

An effective solution to perform component-functional tests in pipeline

Image Credits: author derivative work of Wiremock logo and Docker logo (flickr.com)

One of the most complicated problems for a QA Engineer is to build an effective and maintainable environment to perform Component Functional tests over the system under test (SUT).

Furthermore integrating this kind of test into the pipeline is becoming a must if you want to follow a Continuous Delivery approach, reaching a high level of software quality.

Why isolation?

In the Microservices world, your applications have typically to deal with many downstream services that are out of your control in terms of availability, response time, modifications over time and so on.

Having an isolated environment means having an ideal place where all the world around SUT is working exactly as we expect anytime and the SUT will be the only component that needs our attention. This is how “Component Testing” should be performed.

What are really the advantages of having this type of environment?

You can perform reliable Functional Tests (black-box):

“Functional Testing” should focus on “what” the system is doing. With an isolated environment, if you have a failure, it is surely related to your SUT behaviour;

(black-box): should focus on the system is doing. With an isolated environment, if you have a failure, it is surely related to your SUT behaviour; You have the control on external services (happy/sad paths) :

You can make assertions on the exchanged messages between your application and the external world (usually impossible).

You can also change the responses in order to build customised and repeatable tests, defining potentially one test for each use case;

(happy/sad paths) You can make assertions on the exchanged messages between your application and the external world (usually impossible). You can also change the responses in order to build customised and repeatable tests, defining potentially one test for each use case; You add more value to Integration Tests (fail fast) :

Any failure in the isolated environment must be attributed to the SUT.

The other way around, any new failure obtained switching from isolated to integrated environment is automatically attributed to the external service or to the contract with it. So you can speed up the troubleshooting focusing where the issue is actually present.

(fail fast) Any failure in the isolated environment must be attributed to the SUT. The other way around, any new failure obtained switching from isolated to integrated environment is automatically attributed to the external service or to the contract with it. So you can speed up the troubleshooting focusing where the issue is actually present. Make reliable performance tests :

Performance tests are never 100% repeatable because they’re affected by many semi-random variables and processes (e.g. network latency, IO, processing time of external modules, uncontrolled usage of external services during the test).

Therefore, a very foreseeable behaviour around your application, let’s you limit the performance-related variables to a minimum;

More easily identifying the bottlenecks related only to your own code, making the average results more reliable and comparable among repeated executions.

: Performance tests are never 100% repeatable because they’re affected by many semi-random variables and processes (e.g. network latency, IO, processing time of external modules, uncontrolled usage of external services during the test). Therefore, a very foreseeable behaviour around your application, let’s you limit the performance-related variables to a minimum; More easily identifying the bottlenecks related only to your own code, making the average results more reliable and comparable among repeated executions. Speed up the development and debugging of your own code:

Developers are often strictly wired to external services and environments in general, in order to debug the code.

With an isolated environment, they can rely on full working and modifiable interactions among their application and the rest of world, permitting them to also work out of office and even without the network.

Yes but… how can I setup and maintain an environment of this type?

At Expedia Group we like to exploiting the latest technologies and combine them to achieve elegant and flexible solutions.

The Docker & Wiremock solution

Docker allows to ensure consistency across multiple development and release cycles, standardising your environment, especially in pipeline, where the same image is used across every step of the deployment process.

Therefore the first thing is to dockerize your SUT.

In addition, with Docker Compose we can build a self-consistent environment where our SUT and any mocked and dockerized downstream can live.

Wiremock allows to easy create a mocked version of any HTTP downstream simply recording real interactions and the exposing them through a built-in HTTP server.

The following architecture does not pretend to be the unique or the best solution to the problem. But this architecture has a working implementation adopted with success in my team at Hotels.com™️ and it’s modular in terms of adding/removing or enabling/disabling mocked systems, therefore you should easily be able to adopt it for your purposes.

Isolated environment architecture

This is what you need to implement the above architecture:

Your preferred test driver tool (psss… have a look to Karate! )

tool (psss… have a look to The service under test ( SUT ) provided as a Docker image;

) provided as a Docker image; A configurable Docker image with multiple instances of Wiremock ( We’ll call it “ Wiremock-CI ”);

We’ll call it “ ”); A shared volume with the configuration of your mocks;

with the configuration of your mocks; A Docker Compose configuration that describes your isolated environment;

How does it work?

Within Docker Compose, each downstream service is represented by a different instance of Wiremock inside the same dedicated Docker container.

We’ll call this container “Wiremock-CI”.

The SUT can easy interact with Wiremock-CI in a transparent way thanks to the links of docker compose virtual network and the override of the actual downstream endpoints. Something like this:

docker-compose configuration

Now the only real problem to solve is how to create reliable mocks within Wiremock-CI. Here is the trick.

A bash script within the Wiremock-CI reads a CSV configuration with an entry for each service to mock:

CSV configuration for Wiremock-CI

As you can see, we specify “isProxying” and “isRecording” options.

Basis on this configuration, Wiremock will be run in the related modalities allowing the call coming from the Test Driver to be passed to the real services and recorded at the same time.

In this case, the downstream responses are recorded and saved in a shared volume on file system.

In this way the Test Driver (on the host) has a mocked version of real downstream responses that can reuse during a next test execution setting the “recording mode” to false.

What do you do when one of your downstream services change?

In this frequent case, what you need to do is simply to delete old mocks of that downstream (act on shared folder) and record the new version of downstream. You’re again ready for next test execution.

Conclusion

This solution has been adopted on pipeline for some backend services at Hotels.com™️ and we are enjoying the benefits, finding bugs in advance and improving the confidence of our releases. Moreover it enables us to adopt Continuous Delivery.