Docker Power for Spring Boot Integration Testing
Spring Boot is a well-known and overpowerful server-side framework. And nowadays it has been used more and more in microservices systems. And no matter how the system is built it has to be tested. So how would you prefer to test your microservices system with hundreds of isolated components? I would like to take a bit of your attention for Spring Boot & Docker combinations for integration testing of different system components
Before going to the real examples I would like to give a bit of the context. Like many other engineers, I encountered problems in the testing of individual microservices in isolation from an entire system. So here are several problems teams faced:
- Almost all business logic was covered only by unit tests, and each change in the code without changes in business logic led to additional overhead with changes in the behavior of many unit tests, which did not make any sense
- On one hand, the company has been fast-growing, therefore the majority of engineers were new and not familiar with the system. On the other hand, some of the guys with most of the knowledge about the system already left, and the rest of them simply didn’t have enough knowledge about already implemented features. Therefore code refactoring was a risky, complicated, and unsafe procedure. So most of the feature decisions have followed the rule “works — do not touch”. Because of it, the technical dept has grown massively
- Most of the “integration tests” were useless, they had a lot of dependencies within other existing and live running systems and because of it the behavior of most of the tests was unpredictable and unstable
So how could I improve the confidence and reliability of our services? As the first step, I decided to study whether anyone already faced the same problem and whether there are ready-made solutions in the modern world of microservice development. I read a lot of articles, and the next J.B. Rainsberger’s and André Schaffer’s articles impressed me the most
They focus on rethinking the classic testing pyramid
Integrated test is a test that will pass or fail based on the correctness of another system
If we consider the microservice structure as one big monolith, then we will conclude that classes in a classical monolithic service perform similar functions as microservices in a modern distributed architecture. And the most significant is testing the interaction between components, microservices in our case. Based on this, the idea of Honeycomb was born
But what can be used to recreate external dependencies? At the moment, the easiest way to configure and deploy components is the Docker, so I began to explore its capabilities for integration testing of services. So I concluded that docker-compose allows you to quite conveniently describe all the dependencies in a single file and run them if necessary
Spring Boot & Maven Docker Compose
Configurations for external dependencies can be described at docker-compose.yaml
file
version: "3"
services:
rabbitmq:
image: "rabbitmq:3-management"
environment:
- RABBITMQ_DEFAULT_USER=guest
- RABBITMQ_DEFAULT_PASS=guest
ports:
- "27403:15672"
- "27404:5672"
localstack:
image: localstack/localstack:0.10.5
environment:
- SERVICES=s3
- DEFAULT_REGION=eu-west-1
- DATA_DIR=/tmp/localstack/data
ports:
- "4572:4572"
- "8055:8080"
volumes:
- './.localstack:/tmp/localstack'
The standard set of different external dependencies used by our services
If the project builds by Maven, the next plugin will take all docker up and down management. It runs all containers before verify
stage and shutdowns them after
<plugin>
<groupId>com.dkanejs.maven.plugins</groupId>
<artifactId>docker-compose-maven-plugin</artifactId>
<version>2.4.0</version>
<configuration>
<composeFile>${project.basedir}/docker-compose.yml</composeFile>
<detachedMode>true</detachedMode>
<removeVolumes>true</removeVolumes>
</configuration>
<executions>
<execution>
<id>docker-compose-up</id>
<phase>pre-integration-test</phase>
<goals>
<goal>up</goal>
</goals>
</execution>
<execution>
<id>docker-compose-down</id>
<phase>post-integration-test</phase>
<goals>
<goal>down</goal>
</goals>
</execution>
</executions>
</plugin>
And as always the decision had its pros and cons
Pros:
- Single description place of all dependencies
- All data migrations are isolated and run only once
- The solution allows you to deploy the service locally along with all the necessary external dependencies
Cons:
- It is necessary to clear containers contents after every test run
- The more complex process of running tests in the IDE
- Constant ports conflicts
The current solution already solves all mentioned above problems, however, it has some drawbacks. Therefore I come back to the alternatives research. And this led me to a rather interesting project — Testcontainers. The main idea is elegant and super simple, why do we need to use static YAML files when we can control the container’s dynamic life cycle from code
Spring Boot & Testcontainers
The current approach requires an additional dependency at the project pom file and integration of all external components in the tests codebase
<dependency> <groupId>org.testcontainers</groupId> <artifactId>testcontainers</artifactId> <version>1.14.2</version> <scope>test</scope> </dependency>
public class RedisBackedCacheIntTestStep0 { private RedisBackedCache underTest; @Before public void setUp() { underTest = new RedisBackedCache("localhost", 6379); } @Test public void testSimplePutAndGet() { underTest.put("test", "example"); String retrieved = underTest.get("test"); assertEquals("example", retrieved); } }
Although the current solution did solve some issues, it still had several drawbacks
Pros:
- All data migrations are isolated and run only once
- New containers will be launched for each new Spring Context
- Run tests from the IDE in one click
Cons:
- Bulky constructions to describe container launch rules
- Opaque tincture of Spring Application Context files
The combination of Testcontainers and Spring Boot seems to be super promising and I decided to dig deeper to solve existing shortcomings
Spring Boot & Testcontainers Spring Boot Library
The result of all investigations and researches in the implementation of the next library — Testcontainers for Spring Boot
If the service external dependency is in the supported technologies list, then all you need to do is to add a maven dependency to your pom.xml
and provide application configuration to application-test.yaml
The library will take care of the rest of the work
<dependency> <groupId>com.playtika.testcontainers</groupId> <artifactId>embedded-rabbitmq</artifactId> <version>1.67</version> </dependency>
spring: rabbitmq: host: ${embedded.rabbitmq.host} port: ${embedded.rabbitmq.port} username: ${embedded.rabbitmq.user} password: ${embedded.rabbitmq.password}
The configuration of RabbitMQ for Spring Test Context
Pros:
- All data migrations are isolated and run only once
- New containers will be launched for each new Spring Context
- Run tests from the IDE in one click
Cons:
- Not all technologies are supported by the library
Solved problems:
- A lot of useless unit tests have been removed. Only critical parts of the implementation are covered by unit tests
- The refactoring process is more confident and prevents a lot of problems before service deployment to a real environment
- All integrated tests have been modified to be independent of external systems and performed stable and constant regardless of the state of a third-party system
Conclusion
This trip was exciting and help to solve many of the problems of the existing product. As an ideal option for developing microservice integration testing, I would recommend using a combination of all the approaches above, docker-compose for local deployment of dependencies, Testcontainers Spring Boot Library for integration testing, and pure Testcontainers for dependencies that are not yet supported by the library
Relevant links for further reading
- I have found inspiration in J.B. Rainsberger’s article Integrated Tests Are A Scam and André Schaffer’s Testing of Microservices, maybe it will inspire you as well
- Is it your first time with Docker? https://docs.docker.com/get-docker/
- Do you want to know more about Testcontainers? https://www.testcontainers.org/
- Or maybe you already know it and want to try something more Spring Boot oriented?
https://github.com/testcontainers/testcontainers-spring-boot - And Spock Framework http://spockframework.org/ is super nice for parametrized tests