Integration tests with Testcontainers

Spring Boot · Advanced · 14 min

Tests against H2 miss database-specific behaviour; Testcontainers makes real dependencies cheap.

In-memory databases behave differently from PostgreSQL or MySQL (SQL dialect, constraints, JSON types, locking), so tests pass and production fails. Testcontainers starts real services in throwaway Docker containers during tests.

Spring Boot integrates it tightly. Add spring-boot-testcontainers and the module you need (org.testcontainers:postgresql), then declare the container as a bean or static field annotated @ServiceConnection. Boot reads the container's host, port and credentials and configures the DataSource (or Kafka, Redis, MongoDB, RabbitMQ connections) automatically; no @DynamicPropertySource boilerplate.

Patterns:

  • Annotate a test class with @Testcontainers and a static @Container @ServiceConnection PostgreSQLContainer<?> field; the container starts once for the class.
  • Share containers across test classes by putting @Bean @ServiceConnection definitions in a @TestConfiguration class and importing it, so Spring's context cache reuses them.
  • Combine with @DataJpaTest and @AutoConfigureTestDatabase(replace = NONE) for fast repository tests against the real database, running your Flyway migrations.

You can also use the same configuration at development time: a TestApplication main in src/test that calls SpringApplication.from(App::main).with(ContainersConfig.class).run(args) starts the app with its containers, replacing hand-maintained docker-compose files. Boot also supports Docker Compose directly with spring-boot-docker-compose.

Example

@TestConfiguration(proxyBeanMethods = false)
class ContainersConfig {
    @Bean
    @ServiceConnection                          // Boot wires the DataSource from it
    PostgreSQLContainer<?> postgres() {
        return new PostgreSQLContainer<>("postgres:17-alpine");
    }
}
@SpringBootTest
@Import(ContainersConfig.class)
class BookRepositoryIT {
    @Autowired BookRepository repo;
    @Test
    void findsBooksCaseInsensitively() {
        repo.save(new Book("Dune", "Frank Herbert", LocalDate.of(1965, 8, 1)));
        assertThat(repo.findByAuthorIgnoreCase("frank herbert")).hasSize(1);
    }
}
// src/test/java/.../TestShopApplication.java: run the app locally with containers
class TestShopApplication {
    public static void main(String[] args) {
        SpringApplication.from(ShopApplication::main).with(ContainersConfig.class).run(args);
    }
}

Remember

  • Testcontainers runs real dependencies in Docker for tests
  • @ServiceConnection configures connection properties automatically
  • Put containers in a shared @TestConfiguration to reuse them
  • The same setup can run the app locally

Watch out for

  • Starting a new container in every test class without sharing, making the suite slow.

Check yourself

  1. What does @ServiceConnection on a PostgreSQLContainer bean do?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.spring.io/spring-boot/reference/testing/testcontainers.html