Integration tests have a reputation for being slow, flaky, and hard to maintain. Most of that reputation comes from one problem: managing test infrastructure. Testcontainers solves this elegantly.
The Old Way
Before Testcontainers, your options were:
- H2 in-memory database — fast but behavior differs from production PostgreSQL
- Shared test database — tests interfere with each other, CI becomes a bottleneck
- Docker Compose scripts — complex setup, hard to keep in sync with code
None of these are satisfactory for a serious project.
Enter Testcontainers
Testcontainers spins up real Docker containers for your tests. Each test class (or test suite) gets its own isolated infrastructure:
@SpringBootTest
@Testcontainers
class OrderRepositoryTest {
@Container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16-alpine")
.withDatabaseName("orders_test")
.withUsername("test")
.withPassword("test");
@DynamicPropertySource
static void configure(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Autowired
private OrderRepository orderRepository;
@Test
void shouldPersistAndRetrieveOrder() {
Order order = new Order("ITEM-001", BigDecimal.valueOf(99.99));
orderRepository.save(order);
Optional<Order> found = orderRepository.findById(order.getId());
assertThat(found).isPresent();
assertThat(found.get().getItemCode()).isEqualTo("ITEM-001");
}
}
Beyond Databases
Testcontainers supports dozens of modules:
- Kafka — test event-driven architectures with a real broker
- Redis — verify caching behavior against the actual engine
- LocalStack — simulate AWS services (S3, SQS, DynamoDB) locally
- Elasticsearch — test full-text search with production-grade indexing
Performance Tips
- Use
@Containerwithstaticfields to share containers across tests in a class - Use reusable containers (
withReuse(true)) during local development - Run tests in parallel with separate containers for true isolation
The Result
After adopting Testcontainers across three projects, our flaky test rate dropped from ~8% to near zero. Build times increased by 30 seconds (container startup), but we eliminated hours of debugging false failures.
The trade-off is overwhelmingly positive.