Testcontainers with Spring Boot
AdvancedTestcontainers spin up real Docker containers (Postgres, Kafka, Redis) for integration tests; the @Testcontainers + @Container annotations manage lifecycle.
Overview
Testcontainers is a Java library that spins up real Docker containers during tests, giving you an actual Postgres, Kafka, Redis, or any other service instead of in-memory fakes. This eliminates the "works on my machine" class of bugs caused by differences between H2 and real Postgres, or between embedded brokers and real Kafka. Spring Boot 3.1+ has first-class Testcontainers support via `@ServiceConnection` — it auto-configures the datasource/broker URL from the container's mapped port with zero boilerplate. Containers are started once per test class (or shared with `@Container` as a static field) and stopped after. The `spring-boot-testcontainers` module provides the `@ServiceConnection` annotation and Spring Boot's `@ImportTestcontainers` for reuse.
Basic Setup with @ServiceConnection (Spring Boot 3.1+)
`@ServiceConnection` is the zero-boilerplate way: it reads the container's mapped port and overrides the relevant auto-configuration properties (datasource URL, Kafka bootstrap-servers, Redis host/port) automatically. No `@DynamicPropertySource` required.
@SpringBootTest
@Testcontainers
class OrderServiceIntegrationTest {
@Container
@ServiceConnection // auto-configures spring.datasource.url from the container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16");
@Container
@ServiceConnection // auto-configures spring.kafka.bootstrap-servers
static KafkaContainer kafka =
new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.6.0"));
@Autowired OrderService orderService;
@Autowired OrderRepository orderRepo;
@Test
void shouldPersistOrder() {
Order saved = orderService.place(new CreateOrderRequest("SKU-1", 2));
assertThat(orderRepo.findById(saved.getId())).isPresent();
}
}Reusable Containers with @ImportTestcontainers
Declare containers in a configuration class and annotate tests with `@ImportTestcontainers` so all tests share the same containers (started once per JVM run). This dramatically speeds up test suites by avoiding repeated container start/stop. Combine with `@ActiveProfiles("test")` to keep integration tests separate from unit tests.
// Shared container configuration
class TestContainersConfig {
@Bean
@ServiceConnection
PostgreSQLContainer<?> postgresContainer() {
return new PostgreSQLContainer<>("postgres:16")
.withInitScript("schema.sql"); // run DDL on startup
}
@Bean
@ServiceConnection
RedisContainer redisContainer() {
return new RedisContainer(RedisContainer.DEFAULT_IMAGE_NAME.withTag("7"));
}
}
// Import in tests — containers are shared across all annotated tests
@SpringBootTest
@ImportTestcontainers(TestContainersConfig.class)
class UserServiceIntegrationTest {
@Autowired UserService userService;
@Test
void shouldCacheUser() {
userService.findById(1L); // first call — DB hit
userService.findById(1L); // second call — Redis cache hit
}
}Dynamic Properties (Pre-3.1) and Custom Init Scripts
Before Spring Boot 3.1, use `@DynamicPropertySource` to register container URLs into the Spring environment. For databases, run migration scripts on the container using `withInitScript()` or `withCopyFileToContainer()`. Testcontainers reuses containers between runs if `TESTCONTAINERS_REUSE_ENABLE=true` is set in `~/.testcontainers.properties` — eliminates startup time during local TDD loops.
// Pre-3.1: @DynamicPropertySource
@SpringBootTest
@Testcontainers
class LegacyIntegrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test")
.withInitScript("db/schema.sql"); // classpath resource
@DynamicPropertySource
static void registerProps(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
}
# ~/.testcontainers.properties — enable container reuse in local dev
testcontainers.reuse.enable=trueKey Points to Remember
- 1Testcontainers starts real Docker containers for integration tests — no fake in-memory DBs
- 2@ServiceConnection (Boot 3.1+) auto-configures datasource URL / broker address from the container
- 3Declare containers as static fields so they start once per test class, not per test method
- 4@ImportTestcontainers shares a container configuration class across multiple test classes
- 5TESTCONTAINERS_REUSE_ENABLE=true skips restart between runs — speeds up local TDD
- 6Use @DynamicPropertySource for pre-3.1 Spring Boot to feed container URLs to the context
Interview Questions
Sign in to ask AriaWhy would you use Testcontainers instead of an in-memory H2 database for integration tests?
What does @ServiceConnection do in Spring Boot 3.1 and what did you need before it?
How would you share a single Postgres container across multiple test classes to speed up your suite?
What is the risk of using H2 compatibility mode instead of Testcontainers for Postgres?
How does TESTCONTAINERS_REUSE_ENABLE=true work and when should you not use it?
Ask Aria about Testcontainers with Spring Boot
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.