Testcontainers is a Java library that provides lightweight, disposable instances of databases, message brokers, and other infrastructure components inside Docker containers for testing purposes.
It enables integration tests with real services rather than using in-memory databases or mocks, ensuring tests are closer to production environments.
* Real Dependencies – Run actual databases, message queues, or services inside Docker containers.
* Isolated and Disposable – Each test starts with a fresh environment, ensuring no conflicts.
* Easy Integration – Works seamlessly with JUnit 4, JUnit 5, and Spring Boot.
* Supports Various Technologies – Works with PostgreSQL, MySQL, Redis, Kafka, Selenium, and more.
* Custom Containers – Supports any Docker image, allowing testing with any technology.
Instead of relying on an in-memory database like H2 for tests, Testcontainers allows running a real PostgreSQL instance in a container :
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Testcontainers
class PostgresTest {
@Container
private static final PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:15")
.withDatabaseName("testdb")
.withUsername("testuser")
.withPassword("testpass");
@Test
void testDatabaseConnection() {
assertTrue(postgres.isRunning());
System.out.println("PostgreSQL running on: " + postgres.getJdbcUrl());
}
}
Traditional testing approaches often rely on in-memory databases or shared test environments, leading to inconsistencies between development and production. Testcontainers addresses this by:
* Running real dependencies in Docker containers
* Providing clean, isolated test environments
* Ensuring reliable and repeatable tests
* Supporting integration and end-to-end testing
To use Testcontainers in a Maven project, include the following dependencies in your pom.xml
:
<dependencies>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.19.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.19.3</version>
<scope>test</scope>
</dependency>
</dependencies>
For Gradle, add:
testImplementation 'org.testcontainers:testcontainers:1.19.3'
testImplementation 'org.testcontainers:junit-jupiter:1.19.3'
2. Writing a Simple Test with Testcontainers :
Below is an example of testing a PostgreSQL database using Testcontainers and JUnit 5:
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.junit.jupiter.Container;
import static org.junit.jupiter.api.Assertions.*;
@Testcontainers
class PostgresTest {
@Container
private static final PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:15")
.withDatabaseName("testdb")
.withUsername("testuser")
.withPassword("testpass");
@Test
void testDatabaseConnection() {
assertTrue(postgres.isRunning());
System.out.println("PostgreSQL running on: " + postgres.getJdbcUrl());
}
}
Testcontainers supports various technologies like:
Example: Running a Redis container:
@Container
private static final GenericContainer<?> redis =
new GenericContainer<>("redis:6-alpine").withExposedPorts(6379);
Testcontainers provides powerful features beyond basic containerized testing, making it an essential tool for Java developers working on integration and end-to-end testing.
By default, Testcontainers starts and stops containers for each test, which can be slow. Using Reusable Containers, you can keep containers running across tests to improve performance.
~/.testcontainers.properties
:
testcontainers.reuse.enable=true
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
.withDatabaseName("testdb")
.withUsername("testuser")
.withPassword("testpass")
.withReuse(true);
* This keeps the database running between test executions, reducing setup time.
Testcontainers allows network simulation to test real-world failures like latency, disconnections, or slow responses.
Network network = Network.newNetwork();
@Container
GenericContainer<?> slowService = new GenericContainer<>("my-service:latest")
.withNetwork(network)
.withCommand("tc qdisc add dev eth0 root netem delay 1000ms"); // Add 1s delay
* This simulates network latency, helping test how the application behaves under poor network conditions.
If your application depends on multiple services, Testcontainers can orchestrate multiple containers using docker-compose.yml
.
@Container
static DockerComposeContainer<?> compose =
new DockerComposeContainer<>(new File("src/test/resources/docker-compose.yml"))
.withExposedService("mysql", 3306)
.withExposedService("redis", 6379);
* This is useful for testing microservices or multi-container applications.
Testcontainers integrates with migration tools like Flyway and Liquibase to ensure databases start with the correct schema.
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
.withInitScript("db/migration.sql"); // Run migrations on startup
* Ensures database schema is applied before tests run.
Testcontainers allows running custom services by wrapping any Docker image.
@Container
static GenericContainer<?> apiContainer = new GenericContainer<>("my-custom-api:latest")
.withExposedPorts(8080)
.waitingFor(Wait.forHttp("/health").forStatusCode(200));
* This waits for the API to be healthy before running tests.
Testcontainers provides pre-configured modules for common technologies like:
* Databases: PostgreSQL, MySQL, MongoDB
* Messaging: Kafka, RabbitMQ
* Search: Elasticsearch
* Browser Testing: Selenium
* Cloud Emulators: LocalStack (AWS)
@Container
static KafkaContainer kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.4.1"));
* Makes it easy to test Kafka-based applications.
Testcontainers integrates seamlessly with JUnit 5 to parallelize tests and manage containers automatically.
@Testcontainers
@ExtendWith(TestcontainersExtension.class)
class MyIntegrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
@Test
void testDatabaseConnection() {
assertTrue(postgres.isRunning());
}
}
* The @Container annotation ensures containers start and stop automatically with the test lifecycle.
Spring Boot applications can leverage Testcontainers for integration tests using @Testcontainers
.
@SpringBootTest
@Testcontainers
class MySpringBootTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
}
* Automatically injects database connection details into the Spring Boot application.
Testcontainers is a game-changer for Java testing, providing realistic environments, better isolation, and improved test reliability.
Want to dive deeper? Check out the official docs: Testcontainers Documentation.