On this page
Testcontainers Basics
Testcontainers runs real services (databases, message brokers, etc.) in Docker containers during tests — providing reliable integration testing without mocks.
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>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.19.3</version>
<scope>test</scope>
</dependency>
Requires Docker to be running on the test machine.
PostgreSQL Example
@Testcontainers
class UserRepositoryIT {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
private UserRepository repository;
@BeforeEach
void setup() {
DataSource ds = DataSourceBuilder.create()
.url(postgres.getJdbcUrl())
.username(postgres.getUsername())
.password(postgres.getPassword())
.build();
repository = new UserRepository(ds);
runMigrations(ds);
}
@Test
void shouldSaveAndFindUser() {
User user = new User("Alice", "[email protected]");
repository.save(user);
Optional<User> found = repository.findByEmail("[email protected]");
assertTrue(found.isPresent());
assertEquals("Alice", found.get().getName());
}
}
Singleton Container Pattern
Start one container shared across all test classes:
public abstract class AbstractIntegrationTest {
static final PostgreSQLContainer<?> POSTGRES;
static {
POSTGRES = new PostgreSQLContainer<>("postgres:16-alpine");
POSTGRES.start();
}
}
GenericContainer — Any Docker Image
@Container
static GenericContainer<?> redis = new GenericContainer<>("redis:7-alpine")
.withExposedPorts(6379);
@Test
void shouldConnectToRedis() {
String host = redis.getHost();
int port = redis.getMappedPort(6379);
// connect with host:port
}
Kafka Example
@Container
static KafkaContainer kafka = new KafkaContainer(
DockerImageName.parse("confluentinc/cp-kafka:7.5.0"));
@Test
void shouldPublishAndConsume() {
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafka.getBootstrapServers());
// produce and consume messages
}
Wait Strategies
Control when the container is considered ready:
new GenericContainer<>("my-app:latest")
.waitingFor(Wait.forHttp("/health").forStatusCode(200))
.waitingFor(Wait.forLogMessage(".*Started.*", 1))
.withStartupTimeout(Duration.ofMinutes(2));
Spring Boot Integration
@SpringBootTest
@Testcontainers
class ApplicationIT {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine");
@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);
}
@Test
void contextLoads() { }
}
Best Practices
- Use
@Container staticfor shared containers across tests in a class - Use the singleton pattern for containers shared across test classes
- Pin Docker image versions — avoid
latesttag - Use
@DynamicPropertySourceto inject container connection details into Spring - Run Testcontainers tests separately in CI (slower than unit tests)
- Ensure Docker is available in CI (GitHub Actions:
servicesor Docker-in-Docker)