8.2 Integration Testing with Testcontainers
← Back to Chapter 8: Testing, Deployment & Production-Ready Spring Boot
Using in-memory H2 databases for testing creates a dangerous false sense of security: H2 lacks real PostgreSQL/MySQL JSONB operators, dialect nuances, sequence behavior, and locking mechanics. Testcontainers spins up lightweight, real Docker instances of PostgreSQL, Redis, and Kafka during JUnit tests.
1. Why Testcontainers Beats H2 In-Memory DBs
| Feature | H2 In-Memory Database | Testcontainers (Real Docker DB) |
|---|---|---|
| Fidelity | Emulated, synthetic | 100% exact match with production engine |
| Dialect & Features | Fails on Postgres JSONB, window functions | Full native SQL feature parity |
| Flyway Migrations | Frequently fails on complex vendor scripts | Tests actual Flyway migrations cleanly |
| External Dependencies | Cannot test Kafka, Redis, Elasticsearch | Spawns any official Docker image seamlessly |
2. DynamicPropertySource with PostgreSQL Container
Starting in Spring Boot 3.1+, integration with Testcontainers is completely streamlined via @ServiceConnection:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
class CustomerRepositoryTest {
// Automatically spins up a real PostgreSQL container on an ephemeral Docker port!
@Container
@ServiceConnection // Spring Boot 3.1+ automatically wires datasource URL, username, password!
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine");
@Autowired
private CustomerRepository customerRepository;
@Test
void shouldPersistAndRetrieveCustomer() {
Customer customer = new Customer("Sarwvidya", "sarvy@codewithsarvy.dev");
Customer saved = customerRepository.save(customer);
assertNotNull(saved.getId());
assertEquals("Sarwvidya", customerRepository.findById(saved.getId()).orElseThrow().getFullName());
}
}
3. Testing Event-Driven Systems with KafkaContainer
@Testcontainers
@SpringBootTest
class OrderKafkaEventTest {
@Container
@ServiceConnection
static ConfluentKafkaContainer kafka = new ConfluentKafkaContainer("confluentinc/cp-kafka:7.5.0");
@Autowired
private OrderEventProducer producer;
@Test
void shouldPublishEventToKafka() {
assertDoesNotThrow(() -> producer.publishOrderCreated(new OrderCreatedEvent(UUID.randomUUID())));
}
}
Testcontainers terminates and cleans up the Docker containers automatically when the test JVM process exits.