Command Palette

Search for a command to run...

Hectal
PHASE 6Intermediate ~13 min· topic 4 of 4

Topic 6.4

Testcontainers: Real Dependencies in Your JUnit Tests

In one line

Everything in this course so far runs Docker containers for an APP — Testcontainers runs them for your TEST SUITE, giving JUnit tests a real Postgres/Kafka/Redis instead of a mock, then throwing it away when the test finishes.

0/4 · 0%

Think of it like this

Renting a fully-equipped test kitchen for one afternoon to rehearse a recipe exactly as it'll really be cooked, instead of practicing with a plastic toy stove — a mock database can't catch a real SQL syntax error, a real constraint violation, or a real JDBC driver quirk; a genuine (if temporary) Postgres container can.

Key ideas

  1. 01

    Testcontainers is a Java library that programmatically starts real Docker containers (a specific Postgres version, a real Kafka broker, a real Redis) from WITHIN a JUnit test's lifecycle — the container starts before the test runs, gets a real, isolated instance to talk to over a real driver/client, and the container is destroyed automatically when the test finishes, leaving zero cleanup for you to do by hand.

  2. 02

    This directly replaces the old, worse options: an in-memory fake database (H2 pretending to be Postgres — subtly different SQL dialect, different constraint behavior, catches fewer real bugs) or a shared, always-running test database (state leaks between test runs, and two developers running tests at once can collide with each other's data).

  3. 03

    Because it's a REAL Postgres/Kafka/whatever, running in an ACTUAL Docker container, every mechanism this entire course has covered still applies — it's pulled from a registry (Phase 1), runs as an ephemeral container (Phase 0-1), and can be inspected with docker ps WHILE your test suite is running if you want to see it happening live.

  4. 04

    Spring Boot has first-class Testcontainers integration via the @ServiceConnection annotation (Spring Boot 3.1+) and a @SpringBootTest combined with @Testcontainers — Spring automatically wires the container's real, dynamically-assigned port and credentials into your application context's datasource, with zero manual application-test.properties juggling.

  5. 05

    The one prerequisite that trips people up: Testcontainers needs a working Docker daemon available wherever the tests run — your laptop (fine, Docker Desktop is already running) AND your CI pipeline (Phase 8.2's CI/CD environment must have Docker available too, which most modern CI providers support out of the box, but is worth confirming explicitly).

In your stack

  • →

    A typical real setup: @SpringBootTest + @Testcontainers on an integration test class, a static PostgreSQLContainer field annotated @Container and @ServiceConnection, and the test body just calls your repository/service layer normally — Spring Boot silently points the datasource at the real, running, throwaway Postgres container underneath, and the test asserts against genuine query results.

Code & diagrams

ProductRepositoryIT.javajava

No mocks, no H2 dialect surprises — this hits a real Postgres running in a real, disposable container.

@SpringBootTest
@Testcontainers
class ProductRepositoryIT {

    @Container
    @ServiceConnection
    static PostgreSQLContainer<?> postgres =
        new PostgreSQLContainer<>("postgres:16-alpine");

    @Autowired
    private ProductRepository repository;

    @Test
    void savesAndReadsBackARealRow() {
        Product saved = repository.save(new Product("Keyboard", 4999));

        Product found = repository.findById(saved.getId()).orElseThrow();

        assertThat(found.getName()).isEqualTo("Keyboard");
        // This ran against a genuine Postgres instance —
        // real SQL, real constraints, real JDBC driver behavior.
    }
}

Explain it without notes

01

Why does a test that passes against an H2 in-memory database sometimes fail against real Postgres in production, and how does Testcontainers close that gap?

02

Why does the Postgres container Testcontainers starts need to be thrown away and recreated for a fresh test run, rather than reused indefinitely like a normal long-lived database?

Practice

01

Add the Testcontainers Postgres dependency to any Spring Boot project with a repository layer, write one @SpringBootTest + @Testcontainers integration test, and run it — watch a real, temporary Postgres container appear in docker ps for the duration of the test run.

02

Deliberately write a test assertion that would only fail against REAL Postgres behavior (not H2's approximation) — for example, a genuine unique-constraint violation — and confirm it behaves correctly against the Testcontainers-backed database.

Trade-offs

  • ↔

    Testcontainers-based integration tests are meaningfully SLOWER than pure unit tests or H2-backed tests (starting a real container takes real seconds, not milliseconds) — the right balance is fast, mock-based unit tests for the bulk of your logic, with a smaller, focused set of Testcontainers integration tests specifically covering the real database/broker interaction points where a mock's approximation would be genuinely risky to trust.

Done when you can

  • I understand Testcontainers starts REAL, disposable containers specifically for test runs, not for the app itself.

  • I know why this catches bugs an H2 in-memory substitute would miss.

  • I know Testcontainers requires a working Docker daemon wherever the tests actually run, CI included.