Testcontainers: Real Database Integration Tests
Spin up PostgreSQL in Docker with Testcontainers and run integration tests against a real database.
Testcontainers: Real Database Integration Tests is a free Java Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Java Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Why Testcontainers?
H2 in-memory databases don't support PostgreSQL-specific features (JSON, full-text search, ON CONFLICT). Testcontainers spins up real Docker containers during tests, giving you a production-identical database.
Adding Testcontainers Dependency
Add the Testcontainers BOM and PostgreSQL module to your build file. Spring Boot 3.1+ includes a managed Testcontainers version.
// build.gradle:
testImplementation "org.testcontainers:junit-jupiter"
testImplementation "org.testcontainers:postgresql"
// Optionally use Spring Boot Testcontainers support:
testImplementation "org.springframework.boot:spring-boot-testcontainers"@Testcontainers and @Container
Annotate the test class with @Testcontainers and declare the container as a static @Container field. JUnit 5 starts and stops it automatically.
@Testcontainers
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class UserRepositoryIntegrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine")
.withDatabaseName("testdb")
.withUsername("test")
.withPassword("test");
}Configuring DataSource from Container
Use @DynamicPropertySource to inject the container's dynamic port and credentials into Spring's DataSource configuration.
@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);
}Spring Boot 3.1 ServiceConnection
Spring Boot 3.1+ auto-configures the DataSource from a Testcontainers @Container bean annotated with @ServiceConnection — no need for @DynamicPropertySource.
@Bean
@ServiceConnection
static PostgreSQLContainer<?> postgresContainer() {
return new PostgreSQLContainer<>("postgres:16-alpine");
}Singleton Container Pattern
Starting a container per test class is slow. Share one container across all tests using a static field with Startables.deepStart() or an abstract base class.
abstract class AbstractIntegrationTest {
@Container
static final PostgreSQLContainer<?> POSTGRES =
new PostgreSQLContainer<>("postgres:16-alpine").withReuse(true);
static { POSTGRES.start(); }
@DynamicPropertySource
static void props(DynamicPropertyRegistry r) {
r.add("spring.datasource.url", POSTGRES::getJdbcUrl);
r.add("spring.datasource.username", POSTGRES::getUsername);
r.add("spring.datasource.password", POSTGRES::getPassword);
}
}Testing PostgreSQL-Specific Features
With Testcontainers you can test JSON columns, JSONB operators, full-text search, and ON CONFLICT DO UPDATE — impossible with H2.
@Test
void jsonb_search_works() {
// Insert row with JSONB column, then query
List<Event> logins = repo.findByType("login");
assertEquals(1, logins.size());
}Multiple Containers: Compose
Use DockerComposeContainer to run a full stack (Postgres + Redis + Kafka) for integration tests that require multiple services.
@Container
static DockerComposeContainer<?> compose = new DockerComposeContainer<>(
new File("src/test/resources/docker-compose.yml"))
.withExposedService("postgres", 5432)
.withExposedService("redis", 6379);Ryuk: Automatic Cleanup
Testcontainers uses a Ryuk container to automatically stop and remove containers when the JVM exits — even on test failure, preventing container leaks.
@SpringBootTest with Testcontainers
Use @SpringBootTest instead of @DataJpaTest to test the full application stack (controllers, services, repositories) against a real Postgres container.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
class FullIntegrationTest extends AbstractIntegrationTest {
@LocalServerPort int port;
@Autowired TestRestTemplate rest;
@Test
void end_to_end_user_creation() {
ResponseEntity<UserDto> r = rest.postForEntity("/api/users", createReq(), UserDto.class);
assertEquals(HttpStatus.CREATED, r.getStatusCode());
}
}Container Image Caching
Testcontainers caches pulled Docker images locally. Pull images in CI before tests run (docker pull postgres:16-alpine) to avoid download overhead during the test run.
Quick Check
Which annotation auto-wires a Testcontainers container to Spring's DataSource in Spring Boot 3.1+?
Recap
Testcontainers runs real Docker containers for integration tests. Use @Container + @DynamicPropertySource or @ServiceConnection (Boot 3.1+). Share containers across tests with the singleton pattern. Test PostgreSQL-specific features impossible with H2.
Frequently asked questions
Is the “Testcontainers: Real Database Integration Tests” lesson free?
Yes — the full text of “Testcontainers: Real Database Integration Tests” is free to read here on the web, and the Java Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Java Academy course, upgrade to CoddyKit PRO.
What will I learn in “Testcontainers: Real Database Integration Tests”?
Spin up PostgreSQL in Docker with Testcontainers and run integration tests against a real database. You practise Java Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Java Academy?
No prior experience is required. Java Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Testcontainers: Real Database Integration Tests” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Java Academy lesson?
Yes. Every Java Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Parameterized Tests with @CsvSource and @MethodSource
- Mockito Advanced: Argument Captors and Spies
- Spring Boot Test Slices: @WebMvcTest and @DataJpaTest
- Testcontainers: Real Database Integration Tests