Java > Testing in Java > Integration Testing > Test Containers
Integration Testing with Testcontainers: PostgreSQL Example
This example demonstrates how to use Testcontainers in Java to run integration tests against a real PostgreSQL database instance. Using Testcontainers ensures a consistent and isolated testing environment for each test run.
Dependencies Setup (pom.xml)
This section shows the necessary Maven dependencies to include in your `pom.xml` file. We need the `postgresql` dependency from Testcontainers, JUnit Jupiter for writing tests, and the PostgreSQL JDBC driver to connect to the database.
<!-- Testcontainers Dependency -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.19.6</version>
<scope>test</scope>
</dependency>
<!-- JUnit Jupiter (for testing framework) -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.11.0-M1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.11.0-M1</version>
<scope>test</scope>
</dependency>
<!-- Assuming you're using JDBC -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.3</version>
</dependency>
Java Code: PostgreSQL Container and Test
This code defines an integration test using Testcontainers and JUnit 5. The `@Testcontainers` annotation enables automatic startup and shutdown of the container. The `@Container` annotation declares a static `PostgreSQLContainer` instance that Testcontainers will manage. The `testDatabaseConnection` method establishes a JDBC connection to the PostgreSQL container using the dynamically allocated port and credentials, executes a simple SQL query, and asserts that the returned value is correct.
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import static org.junit.jupiter.api.Assertions.assertEquals;
@Testcontainers
public class PostgreSQLIntegrationTest {
@Container
private static final PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine");
@Test
void testDatabaseConnection() throws SQLException {
String jdbcUrl = postgres.getJdbcUrl();
String username = postgres.getUsername();
String password = postgres.getPassword();
try (Connection connection = DriverManager.getConnection(jdbcUrl, username, password)) {
Statement statement = connection.createStatement();
statement.execute("CREATE TABLE IF NOT EXISTS test_table (id SERIAL PRIMARY KEY, value VARCHAR(255))");
statement.execute("INSERT INTO test_table (value) VALUES ('Test Value')");
ResultSet resultSet = statement.executeQuery("SELECT value FROM test_table WHERE id = 1");
resultSet.next();
String value = resultSet.getString("value");
assertEquals("Test Value", value);
}
}
}
Concepts Behind the Snippet
Testcontainers: A Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container.
Integration Testing: Testing that verifies the interaction between different components or services in a system. In this case, it tests the interaction between your Java application and a PostgreSQL database.
Docker Container: A standardized unit of software that packages up code and all its dependencies so the application runs quickly and reliably from one computing environment to another.
Real-Life Use Case
Imagine you're developing a web application that relies on a PostgreSQL database. Instead of using a shared development database, or relying on a potentially inconsistent local database, you can use Testcontainers to spin up a fresh PostgreSQL instance for each integration test. This ensures that your tests are isolated and reproducible, regardless of the environment.
Best Practices
Interview Tip
Be prepared to explain the benefits of using Testcontainers for integration testing, such as improved test reliability, isolation, and reproducibility. Also, understand the trade-offs in terms of increased test execution time and resource consumption compared to simpler unit tests.
When to use them
Use Testcontainers when you need to test your application's interaction with external services like databases, message queues, or other Docker-based applications. It's particularly valuable for integration tests and end-to-end tests.
Memory Footprint
The memory footprint depends on the specific container being used. PostgreSQL containers generally require more memory than simpler containers. Be mindful of resource constraints, especially in CI/CD environments. You can potentially configure the container resource limits (memory, CPU) if needed. Alpine-based images are typically smaller and more efficient than full-sized images.
Alternatives
Alternatives to Testcontainers include using in-memory databases (like H2) for unit tests, mocking external dependencies, or using shared development environments. However, these alternatives often sacrifice fidelity and can lead to issues when the application is deployed to a real environment.
Pros
Cons
FAQ
-
Do I need Docker installed to use Testcontainers?
Yes, Testcontainers relies on Docker to run the containers. You'll need to have Docker installed and running on your machine. -
How do I clean up the containers after the tests?
Testcontainers automatically cleans up the containers after the tests are finished, so you don't need to worry about manual cleanup when using JUnit Jupiter and the `@Testcontainers` annotation. -
Can I use Testcontainers with other testing frameworks besides JUnit?
Yes, Testcontainers supports other testing frameworks like TestNG, but the setup might be slightly different.