1. Introduction
In this tutorial, we’ll be looking at Java TestContainers library. It allows us to use Docker containers within our tests. As a result, we can write self-contained integration tests that depend on external resources.
We can use any resource in our tests that have a docker image. For example, there are images for databases, web browsers, web servers, and message queues. Therefore, we can run them as containers within our tests.
2. Requirements
TestContainers library can be used with Java 8 and higher. Besides, it is compatible with JUnit Rules API.
First, let’s define the maven dependency for the core functionality:
<dependency> <groupId>org.testcontainers</groupId> <artifactId>testcontainers</artifactId> <version>1.8.0</version> </dependency>
There are also modules for specialized containers. In this tutorial, we’ll be using PostgreSQL and Selenium.
Let’s add the relevant dependencies:
<dependency> <groupId>org.testcontainers</groupId> <artifactId>postgresql </artifactId> <version>1.8.0</version> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>selenium </artifactId> <version>1.8.0</version> </dependency>
We can find latest versions on Maven Central.
Also, we need Docker to run containers. Refer to Docker documentation for installation instructions.
Make sure you’re able to run Docker containers in your test environment.
3. Usage
Let’s configure a generic container rule:
@ClassRule public static GenericContainer simpleWebServer = new GenericContainer("alpine:3.2") .withExposedPorts(80) .withCommand("/bin/sh", "-c", "while true; do echo " + "\"HTTP/1.1 200 OK\n\nHello World!\" | nc -l -p 80; done");
We construct a GenericContainer test rule by specifying a docker image name. Then, we configure it with builder methods:
- We use withExposedPorts to expose a port from the container
- withCommand defines a container command. It will be executed when the container starts.
The rule is annotated with @ClassRule. As a result, it will start the Docker container before any test in that class runs. The container will be destroyed after all methods are executed.
If you apply @Rule annotation, GenericContainer rule will start a new container for each test method. And it will stop the container when that test method finishes.
We can use IP address and port to communicate with the process running in the container:
@Test public void givenSimpleWebServerContainer_whenGetReuqest_thenReturnsResponse() throws Exception { String address = "http://" + simpleWebServer.getContainerIpAddress() + ":" + simpleWebServer.getMappedPort(80); String response = simpleGetRequest(address); assertEquals(response, "Hello World!"); }
4. Usage Modes
There are several usage modes of the test containers. We saw an example of running a GenericContainer.
TestContainers library has also rule definitions with specialized functionality. They are for containers of common databases like MySQL, PostgreSQL; and others like web clients.
Although we can run them as generic containers, the specializations provide extended convenience methods.
4.1. Databases
Let’s assume we need a database server for data-access-layer integration tests. We can run databases in containers with the help of TestContainers library.
For example, we fire up a PostgreSQL container with PostgreSQLContainer rule. Then, we’re able to use helper methods. These are getJdbcUrl, getUsername, getPassword for database connection:
@Rule public PostgreSQLContainer postgresContainer = new PostgreSQLContainer(); @Test public void whenSelectQueryExecuted_thenResulstsReturned() throws Exception { String jdbcUrl = postgresContainer.getJdbcUrl(); String username = postgresContainer.getUsername(); String password = postgresContainer.getPassword(); Connection conn = DriverManager .getConnection(jdbcUrl, username, password); ResultSet resultSet = conn.createStatement().executeQuery("SELECT 1"); resultSet.next(); int result = resultSet.getInt(1); assertEquals(1, result); }
It is also possible to run PostgreSQL as a generic container. But it’d be more difficult to configure the connection.
4.2. Web Drivers
Another useful scenario is to run containers with web browsers. BrowserWebDriverContainer rule enables running Chrome and Firefox in docker-selenium containers. Then, we manage them with RemoteWebDriver.
This is very useful for automating UI/Acceptance tests for web applications:
@Rule public BrowserWebDriverContainer chrome = new BrowserWebDriverContainer() .withDesiredCapabilities(DesiredCapabilities.chrome()); @Test public void whenNavigatedToPage_thenHeadingIsInThePage() { RemoteWebDriver driver = chrome.getWebDriver(); driver.get("https://saucelabs.com/test/guinea-pig"); String heading = driver .findElement(By.xpath("/html/body/h1")).getText(); assertEquals("This page is a Selenium sandbox", heading); }
4.3. Docker Compose
If the tests require more complex services, we can specify them in a docker-compose file:
simpleWebServer: image: alpine:3.2 command: ["/bin/sh", "-c", "while true; do echo 'HTTP/1.1 200 OK\n\nHello World!' | nc -l -p 80; done"]
Then, we use DockerComposeContainer rule. This rule will start and run services as defined in the compose file.
We use getServiceHost and getServicePost methods to build connection address to the service:
@ClassRule public static DockerComposeContainer compose = new DockerComposeContainer( new File("src/test/resources/test-compose.yml")) .withExposedService("simpleWebServer_1", 80); @Test public void givenSimpleWebServerContainer_whenGetReuqest_thenReturnsResponse() throws Exception { String address = "http://" + compose.getServiceHost("simpleWebServer_1", 80) + ":" + compose.getServicePort("simpleWebServer_1", 80); String response = simpleGetRequest(address); assertEquals(response, "Hello World"); }
5. Conclusion
We saw how we could use TestContainers library. It eases developing and running integration tests.
We used GenericContainer rule for containers of given docker images. Then, we looked at PostgreSQLContainer, BrowserWebDriverContainer and DockerComposeContainer rules. They give more functionality for specific use cases.
Finally, code samples here can be found over on GitHub.