JUnit 4 Quickstart

It's easy to add Testcontainers to your project - let's walk through a quick example to see how.

Let's imagine we have a simple program that has a dependency on Redis, and we want to add some tests for it. In our imaginary program, there is a RedisBackedCache class which stores data in Redis.

You can see an example test that could have been written for it (without using Testcontainers):

public class RedisBackedCacheIntTestStep0 {
    private RedisBackedCache underTest;

    @Before
    public void setUp() {
        // Assume that we have Redis running locally?
        underTest = new RedisBackedCache("localhost", 6379);
    }

    @Test
    public void testSimplePutAndGet() {
        underTest.put("test", "example");

        String retrieved = underTest.get("test");
        assertEquals("example", retrieved);
    }
}

Notice that the existing test has a problem - it's relying on a local installation of Redis, which is a red flag for test reliability. This may work if we were sure that every developer and CI machine had Redis installed, but would fail otherwise. We might also have problems if we attempted to run tests in parallel, such as state bleeding between tests, or port clashes.

Let's start from here, and see how to improve the test with Testcontainers:

1. Add Testcontainers as a test-scoped dependency

First, add Testcontainers as a dependency as follows:

testCompile "org.testcontainers:testcontainers:1.12.2"
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>testcontainers</artifactId>
    <version>1.12.2</version>
    <scope>test</scope>
</dependency>

2. Get Testcontainers to run a Redis container during our tests

Simply add the following to the body of our test class:

@Rule
public GenericContainer redis = new GenericContainer<>("redis:5.0.3-alpine")
                                        .withExposedPorts(6379);

The @Rule annotation tells JUnit to notify this field about various events in the test lifecycle. In this case, our rule object is a Testcontainers GenericContainer, configured to use a specific Redis image from Docker Hub, and configured to expose a port.

If we run our test as-is, then regardless of the actual test outcome, we'll see logs showing us that Testcontainers:

  • was activated before our test method ran
  • discovered and quickly tested our local Docker setup
  • pulled the image if necessary
  • started a new container and waited for it to be ready
  • shut down and deleted the container after the test

3. Make sure our code can talk to the container

Before Testcontainers, we might have hardcoded an address like localhost:6379 into our tests.

Testcontainers uses randomized ports for each container it starts, but makes it easy to obtain the actual port at runtime. We can do this in our test setUp method, to set up our component under test:

String address = redis.getContainerIpAddress();
Integer port = redis.getFirstMappedPort();

// Now we have an address and port for Redis, no matter where it is running
underTest = new RedisBackedCache(address, port);

Tip

Notice that we also ask Testcontainers for the container's actual address with redis.getContainerIpAddress();, rather than hard-coding localhost. localhost may work in some environments but not others - for example it may not work on your current or future CI environment. As such, avoid hard-coding the address, and use getContainerIpAddress() instead.

4. Run the tests!

That's it!

Let's look at our complete test class to see how little we had to add to get up and running with Testcontainers:

public class RedisBackedCacheIntTest {

    private RedisBackedCache underTest;

    // rule {
    @Rule
    public GenericContainer redis = new GenericContainer<>("redis:5.0.3-alpine")
                                            .withExposedPorts(6379);
    // }


    @Before
    public void setUp() {
        String address = redis.getContainerIpAddress();
        Integer port = redis.getFirstMappedPort();

        // Now we have an address and port for Redis, no matter where it is running
        underTest = new RedisBackedCache(address, port);
    }

    @Test
    public void testSimplePutAndGet() {
        underTest.put("test", "example");

        String retrieved = underTest.get("test");
        assertEquals("example", retrieved);
    }
}