Docker in JUnit tests

October 23, 2017

testcontainers

Unit testing in Java is made simple by libraries such as JUnit and e.g. Mockito, but there’s not the same panacea for integration testing. Alongside your mocked out unit tests, integration tests are important to ensure the correct function of your software when accessing external services. Previously, we have taken different approaches to the problem, such as:

  • Embedding the service
  • Running the service
  • Running a Docker container for the service

Embedding the service can be hard (especially if it is not Java). Running them directly, or remotely, adds significant work to configure, maintain and make available for all developers and your continuous integration service. Running a Docker container for the service was our preferred method but setting them up locally and as part of the CI build seems out of sorts with the fundamental approach to testing. This has been a common and frustrating pain point for us.

Test Containers

We have recently switched to using TestContainers to solve this problem. TestContainers is an MIT licensed open source project for running a Docker container for a JUnit test.

Benefits

This has a number of benefits over our previous approaches. First, we are running the tests against a real instance of the service, not some mocked or embedded version, which may behave differently. As the containers run just for the test they are isolated from other test and become much more reproducible. The setup and configuration are done in the test class, where they belong. It also allows you to easily repeat the test against different versions of the service allowing you to correctly determine a compatibility and check new versions of services do not require changes to your own code.

Use cases

This technique for integration testing can easily be applied to multiple use cases, some of which have more specialised support:

Example

The following example shows how to set up an integration test for Redis.

import org.junit.Rule;
import org.junit.Test;
import org.testcontainers.containers.GenericContainer;

import redis.clients.jedis.Jedis;

public class RedisIntegrationTest {

  private static final String DOCKER_IMAGE = "redis:3.2.9";

  private final Jedis jedis;

  @Rule // 1
  public static GenericContainer redis = new GenericContainer(DOCKER_IMAGE).withExposedPorts(6379); // 2, 3

    @Before
    public void setUp() throws Exception {
        Jedis jedis = new Jedis(redis.getContainerIpAddress(), redis.getMappedPort(6379)); // 4
    }

  @Test
  public void yourTest() {
    //Your test code here
  }

}
  1. @Rule Runs a new container for every test, you can change to @ClassRule for a single container for the test class.
  2. It also supports the use of Dockerfiles or Docker Compose files for non-standard, of not published containers by using DockerComposeContainer.
  3. Other standard docker config like .withVolume(), withEnv(), withCommand() are available.
  4. Obtain the configuration you need from the GenericContainer object.

Disadvantages

None! O.K. maybe there are some disadvantages, but we think they are well worth it:

  • You need docker available to run the tests. (i.e. on your local device and on the CI server) but who doesn’t?
  • You need to make docker available (which may require exposing the docker socket)
  • You have to download the image but this is only needed once.
  • Windows support is only in Alpha.

Credit: Image by Richard North, TestContainers Project.

Work with us

Have a project or an idea? We'd love to talk with you and see if we can help.

GET IN TOUCH
.

3 Strand Court

Bath Road

Cheltenham

Gloucestershire

GL53 7LW

© Committed