Getting Started with Testcontainers for Java
Introduction
Integration testing against in-memory databases like H2 often fails to catch database-specific SQL syntax bugs or transaction locking issues present in production. Testcontainers solves this by providing lightweight, disposable Docker containers for PostgreSQL, MySQL, Kafka, and Redis directly inside your JUnit 5 test cases. In this beginner-friendly tutorial, you will learn how to configure `GenericContainer` and `PostgreSQLContainer` to build reliable integration tests in Java.
What is TestContainers ?
Testcontainers is an open source framework for running lightweight instances of Container Images as part of your Tests.
By utilizing Testcontainers, the need to set up complex environments to test services becomes obsolete. You can define your test dependencies as code, effortlessly execute your tests, and watch as containers are dynamically created and disposed of. With Testcontainers’ broad language and testing framework support, all that’s required is Docker to streamline your testing process.
Setting up Testcontainers
To get started with Testcontainers, you need to include the testcontainers library as a dependency in your Java project.
Firstly, we recommend including the Bill of Materials to hook a specific version of TestContainers for all your libraries:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
<version>1.20.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Then, include the core dependency as well in your pom.xml:
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<scope>test</scope>
</dependency>
Additionally, include JUnit Test library to annotate your Tests. Check the following article to learn more about JUnit configuration: JUnit 5 Made Easy
Example: GenericContainer
The GenericContainer class allows you to start a Docker container with a specified image. Let’s start with a simple example of running an httpd web server using Testcontainers.
@Testcontainers
public class GenericContainerTest {
private static final int HTTP_PORT = 80;
@Container
private static final GenericContainer<?> container = new GenericContainer<>("httpd:latest")
.withExposedPorts(HTTP_PORT);
@Test
public void testHttpdServer() throws Exception {
// Get the host and port of the running container
String host = container.getHost();
int port = container.getMappedPort(HTTP_PORT);
// Create the URL for the HTTP request
URL url = new URL("http://" + host + ":" + port + "/");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// Send an HTTP GET request
connection.setRequestMethod("GET");
connection.connect();
// Verify the response code is 200 (OK)
int responseCode = connection.getResponseCode();
System.out.println("Response code:"+responseCode);
Assertions.assertEquals(200, responseCode);
}
}
In this code snippet:
- The
@Testcontainersannotation enables Testcontainers support for the test class. - The
@Containerannotation defines a static fieldcontainerthat represents the GenericContainer instance. - The
GenericContaineris created with the image name “httpd:latest” and exposes the HTTP_PORT. - Then, we open an HTTP connection and send an HTTP GET request to the container.
- Finally, we verify the response code using
Assertions.assertEquals()to ensure it is 200 (OK).
This code demonstrates the usage of Testcontainers to start a GenericContainer running an Apache HTTP server, perform an HTTP request, and verify the response code.
Another TestExample: A PostgreSQL Container
Besides the GenericContainer there are several pre-built API to Test Services such as Databases, Brokers or Identity Servers. As an example, we will show here how to kickstart and Test a PostgreSQL container with PostgreSQLContainer.
Firstly, we need to add to our project the dependency for PostgreSQL TestContainer and a suitable Driver for PostgreSQL:
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.3.1</version>
<scope>test</scope>
</dependency>
Finally, here is our JUnit 5 Test which starts a PostgreSQL Database and executes a SELECT on the current Data when the Container is running:
@Testcontainers
public class PostgreSQLTest {
@Container
private static final PostgreSQLContainer<?> postgresContainer = new PostgreSQLContainer<>("postgres:latest");
private Connection connection;
@BeforeAll
public static void setUp() {
postgresContainer.start();
}
@BeforeEach
public void connectToDatabase() throws Exception {
String jdbcUrl = postgresContainer.getJdbcUrl();
String username = postgresContainer.getUsername();
String password = postgresContainer.getPassword();
connection = DriverManager.getConnection(jdbcUrl, username, password);
}
@AfterEach
public void closeConnection() throws Exception {
connection.close();
}
@Test
public void testCurrentDateNotNull() throws Exception {
// Execute a query to retrieve the current date from the database
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("SELECT CURRENT_DATE");
// Verify that the result set is not null
assertNotNull(resultSet);
// Move the cursor to the first row
resultSet.next();
// Retrieve the current date value from the result set
LocalDate currentDate = resultSet.getObject(1, LocalDate.class);
System.out.println("Date is " + currentDate);
// Verify that the current date is not null
assertNotNull(currentDate);
}
}
As you can see, the code is much leaner as we reference the org.testcontainers.containers.PostgreSQLContainer which provides details about the JDBC Connection so that we can run a simple Test with it.
Run the above Test in your IDE and verify that the current Data is not null:
Conclusion
In this tutorial, we explored how to use Testcontainers to set up a GenericContainer and a PostgreSQL container for your Java tests. We learned how to include Testcontainers as a dependency, start and stop containers, and perform tests using the containerized dependencies. Testcontainers is a powerful library that simplifies testing by providing disposable containers for your dependencies, enabling you to write more reliable and isolated tests.
By utilizing Testcontainers in your Java tests, you can ensure that your applications are thoroughly tested against real dependencies, leading to more robust and reliable software.
Source code for this article: https://github.com/fmarchioni/mastertheboss/tree/master/test/testcontainer
Common Problems & Solutions
Problem: Test fails with Could not find a valid Docker environment.
Cause: The Docker daemon is either not running or the current user lacks socket permissions.
Solution: Ensure Docker Desktop or the Docker service is running and verify your user is added to the docker group.
Problem: Testcontainers fails to bind random exposed ports.
Cause: Attempting to access hardcoded container ports instead of mapped dynamic host ports.
Solution: Use container.getMappedPort(PORT) or container.getJdbcUrl() rather than localhost static ports.
Recommended Articles
Optimize Your Enterprise Java Applications with Testcontainers and Databases
Learn how to use Testcontainers for accurate database testing in your JPA/Hibernate applications. #TestContainers #Java #Databases #JPA #Hibernate
Execute Tests on Container Images with JBang, TestContainers, and Java
Learn how to run tests on container images using JBang, TestContainers, and Java. Simplify testing with TestContainers.
Effortlessly Manage PostgreSQL Database with Docker Containers - A Comprehensive Guide
Learn how to quickly start and manage a PostgreSQL database using Docker containers in this comprehensive guide.
Enhance EJB Testing with Hibernate for Enterprise Java Applications
Boost your enterprise java applications by easily unit testing EJB components using Hibernate. Learn how to set up an out-of-container persistence model in this tutorial.