Introduction to TestContainers

After we have finished this lesson, we:

  • Know what TestContainers is.
  • Can get the required dependencies with Maven.
  • Understand how we can use generic containers.
  • Can run a PostgreSQL database with TestContainers.
  • Know how we can run a Selenium WebDriver container.

Get the source code from Github

What is TestContainers?

TestContainers allows us to start a Docker container before our test methods are run and stop it after our test methods have been run. At the moment, it requires Docker (or docker-machine), Java 8, and JUnit 4. It’s possible to use TestContainers on Linux, macOs, and Windows. However, keep in mind that the macOS and Windows support isn’t perfect.

The TestContainers library has five usage modes:

  • We can run a generic container.
  • We can run a Docker container that starts a database. At the moment TestContainers supports the MySQL, PostgreSQL, Oracle XE, and Virtuoso databases.
  • We can start a Docker container that starts a web browser. After we have started this container, we can control the web browser by using a remote WebDriver.
  • We can run a Docker container that starts the services defined in a Docker Compose file.
  • We can run a Docker container that is created from a Dockerfile.
This lesson concentrates on the first three usage modes. If you want to get more information about the advanced usage modes, you should take a look at the TestContainers User’s Manual .

Next, we will find out how we can get the required dependencies with Maven.

Getting the Required Dependencies With Maven

We can get the required dependencies with Maven by adding the following dependencies to our POM file:

  • The testcontainers module contains the core functionality, and provides support for generic containers and Docker Compose.
  • The postgresql module provides support for running the PostgreSQL database.
  • The selenium module provides support for running Selenium WebDriver containers.

In other words, we can get the required dependencies by adding the following snippet to the dependencies section of our POM file:

 1<dependency>
 2    <groupId>org.testcontainers</groupId>
 3    <artifactId>testcontainers</artifactId>
 4    <version>1.8.0</version>
 5    <scope>test</scope>
 6</dependency>
 7<dependency>
 8    <groupId>org.testcontainers</groupId>
 9    <artifactId>postgresql</artifactId>
10    <version>1.8.0</version>
11    <scope>test</scope>
12</dependency>
13<dependency>
14    <groupId>org.testcontainers</groupId>
15    <artifactId>selenium </artifactId>
16    <version>1.8.0</version>
17    <scope>test</scope>
18</dependency>

Let’s move on and find out how we can configure the started Docker container.

Configuring the Started Docker Container

We can configure the started Docker container by using JUnit 4 rules. When we configure the started Docker container, we have to use one of these two options:

  • If we want that the started Docker container is started once before any test method is run, we have to add a public and static field to our test class, and annotate this field with the @ClassRule annotation.
  • If we want that the started Docker container is started before a test method is run, we have to add a public field to our test class, and annotate this field with the @Rule annotation.
Most of the time we should start our Docker containers only once because starting them before every test method can make our test suite too slow. That’s why all examples of this lesson start the Docker container only once.

Next, we will find out how we can run generic containers.

Running Generic Containers

If we want to run a generic container, we have to use the GenericContainer rule. When we create a new GenericContainer object, we have to pass the name of the started Docker image as a constructor argument. After we have created a new GenericContainer object, we can configure the started Docker container by using the methods of the GenericContainer class.

Let’s write one test method which uses a Docker container that runs a minimal Docker image based on Alpine Linux. This Docker container exposes the port 80. When we send an HTTP request to the exposed port, the started Docker container returns the HTTP status code 200 and the message: ‘TestContainers Rules!’.

We can write this test method by following these steps:

First, we have to configure the GenericContainer rule that starts our Docker container. When we configure this rule, we have to configure the exposed port and the command that returns the HTTP response which contains the correct message.

After we have configured the GenericContainer rule, the source code of our test class looks as follows:

 1import org.junit.ClassRule;
 2import org.testcontainers.containers.GenericContainer;
 3
 4public class ClassRuleGenericContainerTest {
 5
 6    @ClassRule
 7    public static GenericContainer webServer = new GenericContainer(
 8            "alpine:3.5"
 9    )
10            .withExposedPorts(80)
11            .withCommand("/bin/sh",
12                    "-c",
13                    "while true; do echo \"HTTP/1.1 200 OK\n\n" +
14                            "TestContainers Rules!\" | nc -l -p 80; done"
15            );

Second, we have to write a test method which ensures that our Docker container returns the correct message when we send a GET request to the exposed port.

After we written our test method, the source code of our test class looks as follows:

 1import org.apache.http.HttpResponse;
 2import org.apache.http.client.methods.HttpGet;
 3import org.apache.http.impl.client.CloseableHttpClient;
 4import org.apache.http.impl.client.HttpClients;
 5import org.apache.http.util.EntityUtils;
 6import org.junit.ClassRule;
 7import org.junit.Test;
 8import org.testcontainers.containers.GenericContainer;
 9
10import java.io.IOException;
11
12import static org.assertj.core.api.Assertions.assertThat;
13
14public class ClassRuleGenericContainerTest {
15
16    @ClassRule
17    public static GenericContainer webServer = new GenericContainer(
18            "alpine:3.2"
19    )
20            .withExposedPorts(80)
21            .withCommand("/bin/sh",
22                    "-c",
23                    "while true; do echo \"HTTP/1.1 200 OK\n\n" +
24                            "TestContainers Rules!\" | nc -l -p 80; done"
25            );
26
27    @Test
28    public void shouldReturnCorrectMessage() throws IOException {
29        CloseableHttpClient httpclient = HttpClients.createDefault();
30        HttpGet httpGet = new HttpGet("http://" +
31                webServer.getContainerIpAddress() +
32                ":" + webServer.getMappedPort(80)
33        );
34        HttpResponse resp = httpclient.execute(httpGet);
35
36        String greeting = EntityUtils.toString(resp.getEntity()).trim();
37        assertThat(greeting).isEqualTo("TestContainers Rules!");
38    }
39}

I want to point out two things before we continue this lesson:

  • If we want to get the IP address of the started Docker container, we have to invoke the getContainerIpAddress() method of the GenericContainer class.
  • If we want to get the actual port that allows us to connect to the exposed port, we have to invoke the getMappedPort() method of the GenericContainer class. When we invoke this method, we have to pass the exposed port as a method parameter.

Additional Reading:

Let’s move on and find out how we can run PostgreSQL databases with TestContainers.

Running a PostgreSQL Database

If we want to run a container that runs a PostgreSQL database, we have to use the PostgreSQLContainer rule. The PostgreSQLContainer class has two constructors:

  • The no-argument constructor starts PostgreSQL 9.6.8.
  • If we want to specify the version of the started PostgreSQL database, we have to use the constructor that takes the name of the started Docker image as a constructor argument.

After we have created a new PostgreSQLContainer object, we can configure the started database by using the methods of the PostgreSQLContainer class.

Let’s write a test method that uses the default PostgreSQL version. We can write this test method by following these steps:

First, we have to configure the PostgreSQLContainer rule that starts our PostgreSQL database. After we have configured this rule, the source code of our test class looks as follows:

1import org.junit.ClassRule;
2import org.testcontainers.containers.PostgreSQLContainer;
3
4public class PostgreSQLContainerTest {
5
6    @ClassRule
7    public static PostgreSQLContainer postgreSQL = new PostgreSQLContainer();
8}

Second, we have to write the test method which ensures that the PostgreSQL database was started successfully. After we have written this test method, the source code of our test class looks as follows:

 1import org.junit.ClassRule;
 2import org.junit.Test;
 3import org.testcontainers.containers.PostgreSQLContainer;
 4
 5import java.sql.Connection;
 6import java.sql.DriverManager;
 7import java.sql.ResultSet;
 8import java.sql.SQLException;
 9
10import static org.assertj.core.api.Assertions.assertThat;
11
12public class PostgreSQLContainerTest {
13
14    @ClassRule
15    public static PostgreSQLContainer postgreSQL = new PostgreSQLContainer();
16
17    @Test
18    public void shouldRunPostgreSQLContainer() throws SQLException {
19        Connection conn = DriverManager.getConnection(
20                postgreSQL.getJdbcUrl(),
21                postgreSQL.getUsername(),
22                postgreSQL.getPassword()
23        );
24
25        ResultSet resultSet = conn.createStatement()
26                .executeQuery("SELECT 42");
27        resultSet.next();
28
29        int result = resultSet.getInt(1);
30        assertThat(result).isEqualByComparingTo(42);
31    }
32}

As we can see, we can get the connection details of the started PostgreSQL database by using the getter methods provided by the PostgreSQLContainer class.

Additional Reading:

Next, we will find out how we can run a Selenium WebDriver container.

Running a Selenium WebDriver Container

If we want to run a Selenium WebDriver container, we have to use the BrowserWebDriverContainer rule. After we have created a new BrowserWebDriverContainer object, we can configure the started container by using the withDesiredCapabilities() method of the BrowserWebDriverContainer class.

Let’s write a test method that uses Chrome when it opens the front page of the website of this course. We can write this test method by following these steps:

First, we have to configure the BrowserWebDriverContainer rule that starts the Selenium WebDriver container. When we create a new BrowserWebDriverContainer object, we have to ensure that started container starts the Chrome web browser.

After we have configured the BrowserWebDriverContainer rule, the source code of our test class looks as follows:

 1import org.junit.ClassRule;
 2import org.openqa.selenium.remote.DesiredCapabilities;
 3import org.testcontainers.containers.BrowserWebDriverContainer;
 4public class SeleniumContainerTest {
 5
 6    @ClassRule
 7    public static BrowserWebDriverContainer chrome =
 8            new BrowserWebDriverContainer()
 9                    .withDesiredCapabilities(DesiredCapabilities.chrome());
10}

Second, we have to write a test method that opens the front page of the website of this course and ensures that the opened page has the correct title.

After we have written this test method, the source code of our test class looks as follows:

 1import org.junit.ClassRule;
 2import org.junit.Test;
 3import org.openqa.selenium.remote.DesiredCapabilities;
 4import org.openqa.selenium.remote.RemoteWebDriver;
 5import org.testcontainers.containers.BrowserWebDriverContainer;
 6
 7import static org.assertj.core.api.Assertions.assertThat;
 8
 9public class SeleniumContainerTest {
10
11    @ClassRule
12    public static BrowserWebDriverContainer chrome =
13            new BrowserWebDriverContainer()
14                    .withDesiredCapabilities(DesiredCapabilities.chrome());
15
16    @Test
17    public void shouldReturnCorrectTitleOfCoursesWebsite() {
18        RemoteWebDriver driver = chrome.getWebDriver();
19        driver.get("https://www.testwithspring.com");
20
21        String title = driver.getTitle();
22        assertThat(title).isEqualTo(
23              "Test With Spring Course — Save Time by Writing Less Test Code"
24        );
25    }
26}

As we can see, we can get the required RemoteWebDriver object by invoking the getWebDriver() method of the BrowserWebDriverContainer class.

Additional Reading:

We can now get the required dependencies with Maven and use the most common usage modes of the TestContainers library. Let’s summarize what we learned from this lesson.

Summary

This lesson has taught us five things:

  • TestContainers allows us to start a Docker container before our test methods are run and stop it after our test methods have been run.
  • At the moment, TestContainers requires Docker (or docker-machine), Java 8, and JUnit 4.
  • If we want to run a generic container, we have to use the GenericContainer rule.
  • If we want to run a container that runs a PostgreSQL database, we have to use the PostgreSQLContainer rule.
  • If we want to run a Selenium WebDriver container, we have to use the BrowserWebDriverContainer rule.

Get the source code from Github