Java Academy · Lesson

Testcontainers: Real Database Integration Tests

Spin up PostgreSQL in Docker with Testcontainers and run integration tests against a real database.

Lesson 4 of 413 steps

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.

Free to start

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

  1. Parameterized Tests with @CsvSource and @MethodSource
  2. Mockito Advanced: Argument Captors and Spies
  3. Spring Boot Test Slices: @WebMvcTest and @DataJpaTest
  4. Testcontainers: Real Database Integration Tests
← Back to Java Academy