Using a containerized database for testing Spring Boot applications.
In this post, I will explain how to create and use a Mysql docker image for testing Spring Boot Applications.
Why not an in-memory database?
If you have developed applications using Spring Boot, you might relate to using in-memory databases for testing; mostly because it is a quick and easy way to have the same testing environment (at least the DBMS) on different machines and have it always initialized to a clean state. However, as in-memory databases are not recommended for production environments you should not be using them for testing either. One of the reasons is that you loose the possibility of using platform-specific features that could potentially bring benefits to your project; Therefore, to be able to run tests, one might end up modifying test code to accomplish the features or just by avoiding tests that use them.
Exceptions are another important factor, after all, we are testing to get as much as possible now and avoid them in production. When using a different database, there is a fair possibility that exceptions are not the same and as features are not completely compatible you might get exceptions on one that you would not get on the other. This makes deployment much difficult as you will likely have to deal with these exceptions on deployment or even worse after your code is in production.
Why a Gradle plugin?
I have been using docker for a while already, but having to run a container, stop it, remove it, all through a dockerfile just for testing was becoming such a long process that I ended up installing the Mysql server on every machine and just leave it running while using it. The problem? No consistent environment across all my computers (and my team’s) that also made me dependent on installation. Researching on how to automate Docker, I found this fantastic plugin for Gradle that simplifies creating and deploying Docker containers from your build.gradle.
On this tutorial, we will use a MySql image to run our database, run tests, clean up and remove the container after finishing running the tests.
First, let’s begin by setting up the plugin on Gradle:
There are two ways, using the plugin DSL (Implementation may change in the future as this project is still incubating)
on other versions, you can add the plugin using the regular buildscript block and no matter what method you use remember to apply the plugin.
Let’s begin setting up the database container by creating a block called dcompose. This block is used to configure the docker containers.
As you can see, we are creating setting the test database container. With the image parameter, we are telling Docker which image the container will run. Next, we set up port bindings for our container, the first section is the host port (the port in which the container will be accessible from on the host machine), and the next section is the port inside the docker container. Now, for the env parameter, we are passing the environment variable with the root password, we can use this env to pass a list of environment variables for our container.
By adding dependsOn startTestDatabaseContainer, we are telling Gradle that we need the testDatabase Container up and running before running our tests. Note that startTestDatabaseContainer refers to the container previously defined, if you want to change your container’s name just remember to modify this to start<container’s name>Container. And by adding finalizedBy removeTestDatabaseContainer we are telling Gradle to stop and remove the database container after all tests finish running. With the doFirst directive, we are setting some environment variables that we can use in our project. DoFirst will be the first step of the task To Dos.
With this simple approach we can run our test always under a known initial state and only using resources while the tests are running. In this repository I set up a very simple example using Spring’s JDBCTemplate.
P.D: Few things about the test code. First, DriverManagerConnectionFactory should not be used in any production environment; Use DBCP or HikariCP instead. Second, by putting the thread to sleep on line, I’m allowing some initialization time; This is only needed for this test as it is very short and by the time it started to run, the Mysql instance was not always ready to accept connections. Finally, none of this code should be used in any production environment, nor I recommend it to use it not been on a test unless it is a very simple demonstration.