Dockerize your integration tests 
Tags: java, docker, testcontainers, tests
Published: 2019-01-04 09:01:38

---
title: Dockerize your integration tests 
published: true
description: Use testcontainers to dockerize your integration tests
tags: java, docker, testcontainers, tests
cover_image: https://rnorth.org/public/testcontainers/logo.png
---

Most of our applications have to talk to a database, a HTTP API, a message broker, a SMTP server, etc… And it's quite complicated to set up a real test environment with those components.

In some cases, we can simply mock those components or have an in-memory one during test execution. For example, [H2](http://www.h2database.com/html/main.html) ou [HSQLDB](http://hsqldb.org/) are in-memory databases well-known for being used during integration tests. However, they are not the one used in production environment and our tests can seem unrepresentative.
Today, it's possible to use all the power of Docker and set up a connected test environment easily thanks to [Testcontainers](https://www.testcontainers.org/).

![testcontainers logo](https://rnorth.org/public/testcontainers/logo.png)

## Testcontainers

Testcontainers allows us to easily manipulate Docker containers during test execution. It uses the Docker client [docker-java](https://github.com/docker-java/docker-java) to communicate with Docker daemon. It works with most operating systems and environments and despite the best-efforts support for Windows, I use it on a daily basis with Docker Toolbox. You can find the matrix compatibility [here](https://www.testcontainers.org/compatibility.html).

When you create a container, Testcontainers will try to connect to the Docker daemon by using `DOCKER_HOST`, `DOCKER_TLS_VERIFY` and `DOCKER_CERT_PATH` variables. These environment variables can be easily overridden in the JVM for example.

### Create a container

Containers are represented with the object *GenericContainer*. It's possible to create a container from an image, a Dockerfile or from a Dockerfile created on the fly. In addition, it's possible to create containers from a [Docker Compose file](https://www.testcontainers.org/usage/docker_compose.html).
For instance, this is an Elasticsearch server created from the image [docker.elastic.co/elasticsearch/elasticsearch:6.1.1](https://hub.docker.com/_/elasticsearch/).

```java
GenericContainer container = new GenericContainer("docker.elastic.co/elasticsearch/elasticsearch:6.1.1")
   .withEnv("discovery.type", "single-node")
   .withExposedPorts(9200)
   .waitingFor(
      Wait
       .forHttp("/_cat/health?v&pretty")
       .forStatusCode(200)
   );
```

We can see that it's fairly easy to provide environment variables to the container with the method `withEnv`. In this case, it's the variable *discovery.type* with value *single-node*.
Next, we make sure that our container is up by making an HTTP call on `/_cat/health` API and having a 200 code response. There are also other strategies to assert a container is running:
* Wait.forLogMessage waits for a log message,
* Wait.forListeningPort waits for a listening port
* Wait.forHealthcheck enables to use [HEALTHCHECK](https://docs.docker.com/engine/reference/builder/#healthcheck) feature from docker.

To finalize the container configuration, our container is exposing internal port 9200 and this is explicitly set with the method `withExposedPorts`. It means that Testcontainers will map this container's port to a random port. It's possible to retrieve mapped port with the method `getMappedPort`otherwise we can define port bindings with the method `setPortBindings`. Here, we expose the port 9200 from our container to port 9200:

```java
container.setPortBindings(Arrays.asList("9200:9200"));
```

That's it! Our Elasticsearch server is ready to be used. To start it up, we simply have to execute the start method:

```java
container.start();
```
At startup, Testcontainers will run a bunch of checks like the docker version or the connection to the registered Docker Registry. This can be blocking if you are working behind a company proxy, so it's possible to disable those checks by creating the file *testcontainers.properties* in the tests resources directory with this content:

```java
check.disable=true
```
At last, we can stop our container with the method stop.

```java
container.stop();
```

This will stop the container and also remove the attached volume. This is great because it prevents having dangling volumes.

##During Tests
One great strength of Testcontainers is its integration with JUnit framework. In fact, GenericContainer objects are [JUnit rules](https://github.com/junit-team/junit4/wiki/rules). It means that their lifecycle is directly bound to the test lifecycle. Thereby, by using the `@Rule` or `@ClassRule` JUnit annotations, our containers will be initialized before the test start-up and stopped at the end of the tests execution.

```java
@ClassRule
public static GenericContainer redis = new GenericContainer("redis:3.0.2")
               .withExposedPorts(6379);
```

Nevertheless, it means that Testcontainers will come with a JUnit 4 dependency and it can be annoying if your tests run with [JUnit 5](https://junit.org/junit5/docs/current/user-guide/#overview-what-is-junit-5). Indeed, JUnit has replaced the Rule concept with [Extension](https://junit.org/junit5/docs/current/user-guide/#extensions). Since the version 1.10.0 released on November 2018, Testcontainers [supports now JUnit 5](https://www.testcontainers.org/usage.html#junit) and it's possible to use extensions with the help of `@Testcontainers`and `@Container` annotations from the dedicated library junit-jupiter:

```xml
<dependency>
 <groupId>testcontainers</groupId>
 <artifactId>junit-jupiter</artifactId>
 <version>1.10.2</version>
</dependency>
```

##Preconfigured container
Like Docker, Testcontainers ecosystem is very rich. You can find preconfigured containers like MySql, PostgreSQL, Oracle database, Kafka, Neo4j, Elasticsearch, etc.

```java
@Rule
public KafkaContainer kafka = new KafkaContainer();
```

You can browse the list directly from [maven repository](https://search.maven.org/search?q=g:org.testcontainers).


#A concrete case
Let's see a concrete example of using Testscontainers with the [Spring PetClinic application](https://github.com/spring-projects/spring-petclinic). It's a demonstrating project based on several Spring components like Spring Boot, Spring MVC and Spring JPA. This application aims at managing a pet clinic with pets, pet owners and vets.

![application petclinic](https://thepracticaldev.s3.amazonaws.com/i/6dmhjw1jun0azij8d3zk.png)

The controller layer exposes HTTP endpoints to create and read entities. Then, the persistence layer communicates with a relational database. The application can be configured to communicate with a HSQLDB or a MySql database.

The persistence layer is tested with integration tests and those uses an in-memory HSQL database while the persistence layer itself uses a MySql database.

##Requirements
First, we have to install Docker on the machine which is going to execute tests. Then, we need to add the Testcontainers dependency to the project. In this case, we simply add the following to the *pom.xml* file:

```xml
<dependency>
  <groupId>org.testcontainers</groupId>
  <artifactId>testcontainers</artifactId>
  <version>1.10.2</version>
  <scope>test</scope>
</dependency>
```

## Database configuration
The default database configuration is done in the *application.properties* file.

```
database=hsqldb
spring.datasource.schema=classpath*:db/${database}/schema.sql
spring.datasource.data=classpath*:db/${database}/data.sql
```

As we can see, this is an in-memory HSQLDB database initialized with a schema from the *schema.sql* file. Then, the database is populated with the *data.sql* file. This is the default project configuration.
We need to create *application-test.properties* file to configure a connection to a MySql database.

```
spring.datasource.url=jdbc:mysql://localhost/petclinic
spring.datasource.username=petclinic
spring.datasource.password=petclinic
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
```

Next, let's take the test class `ClinicServiceTests.java`. This class contains all integration tests for the persistence layer. First of all, we need to change Spring test configuration to ensure that the tests will use our database connection.

{% gist https://gist.github.com/vgallet/cfd2f56c7f67de74bcf50e3355239844 %}

The `@TestPropertySource` annotation enables to load our file *application-test.properties* and `@AutoConfigureTestDatabase` with the `NONE` value prevents Spring from creating an embedded database.

## MySql container
Let's create a MySql database that matches requirements from our tests. In this instance, we use the ability from Testcontainers to create a Docker image from a Dockerfile created on the fly. As a first step, we have pulled a [MySql official image](https://hub.docker.com/_/mysql/) from Docker Hub:

{% gist https://gist.github.com/vgallet/640514a92f33bf1ead299bfc8d52a5fb %}

Now, we have to create our database and the connection's user. This is done by using environment variables from the Docker image.

{% gist https://gist.github.com/vgallet/9a052003114ce49ff71d2eb8c18450b1 %}


Next, we have to create a database schema and populate the database. From the image documentation, the directory */docker-entrypoint-initdb.d* is scanned at startup and all files with *.sh*, *.sql* et *.sql.gz* extension are executed. So, we just have to put our files *schema.sql* and *data.sql* in this directory.

{% gist https://gist.github.com/vgallet/98292e5c40aa4ee4d174a43ce3c3d6f1 %}

By using `withClasspathResourceMapping`, the files *schema.sql* and *data.sql* are put on the classpath into the container as a volume. Then, we can access it into our Dockerfile construction.
One last thing, we have to expose the default MySql port: 3306.

{% gist https://gist.github.com/vgallet/d1d115804beef106c0e8e99f2d7aa36f %}

Unfortunately, we can't directly set port bindings with the method *setPortBindings*. We have to customize the container on creation with the method `withCreateContainerCmdModifier`. Finally, we are waiting for the listening port to ensure that our container is up.
Voilà! With few lines of code, we have easily set a MySql database for our tests without having to manage the container lifecycle. The `@ClassRule` annotation makes our container starting once for all the tests. You might be wondering: have we extend the test execution time? In fact, it only takes 907 ms with a Docker container against 860 ms with a HSQLDB in-memory database. The source code shown in this section is available on [github](https://github.com/vgallet/spring-petclinic/tree/testcontainers).


*A big thanks to [Sonyth](https://www.linkedin.com/in/sonyth-huber-57854424/), [Sebastien](https://www.linkedin.com/in/s%C3%A9bastien-bernard-841598176/), [Laurent](https://twitter.com/Ouelcum), [Louis](https://twitter.com/_louidji) and [Nicolas](https://twitter.com/ncuillery) for their time and proofreading.*