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. This is 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, and your progress syncs across the web and the CoddyKit app. The Java Academy course includes 4 lessons in total.
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.
Learn Java with an AI tutor — free
Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.
- Courses
- 104
- Lessons
- 374
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. 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. The Java Academy course includes 4 lessons in total.
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, so you can start here or from the beginning and move at your own pace. This is lesson 4 of 4.
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