Skip to content

Lesson 02 · Docker & Testcontainers

Beyond the 1Z0-830 exam

Integration tests need real dependencies — a database, a message broker, a downstream service. Docker packages those as reproducible containers; Testcontainers starts and stops them from your test code, so each run gets a clean, real dependency and tears it down afterward. This is the production-grade version of Module 16's "test databases."

Objectives

After this lesson you will be able to:

  • Explain Docker images/containers at the level a tester needs.
  • Use Testcontainers to run a real dependency in a test.
  • Explain why this beats both mocks and a shared test server.
  • Understand why these tests need Docker (and so are excluded from the default run).

Docker in one paragraph

A Docker image is an immutable, layered package of an app and everything it needs; a container is a running instance of an image. Because the image pins the OS, runtime, and config, a postgres:16 container behaves the same on your laptop and on the CI runner — reproducibility by construction. You pull images from a registry (Docker Hub) and run them with the Docker engine.

Testcontainers — real dependencies from a test

Rather than running docker run by hand, Testcontainers is a Java library that manages containers within the test lifecycle:

java
@Testcontainers
class OrderRepositoryIT {

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

    @Test
    void persistsAgainstRealPostgres() throws Exception {
        try (Connection conn = DriverManager.getConnection(
                db.getJdbcUrl(), db.getUsername(), db.getPassword())) {
            // ... run the real schema + queries against an actual Postgres ...
        }
    }
}
  • @Container + @Testcontainers start the container before tests and stop it after.
  • getJdbcUrl() exposes a random mapped port, so parallel runs don't collide.
  • The container is thrown away at the end — no leftover state, no shared fixture drift.

There are ready-made modules for Postgres, MySQL, Kafka, Redis, Elasticsearch, and a generic container for anything with an image.

Why it beats the alternatives

ApproachFidelityIsolationCost
Mock/in-memory (H2)low — not the real enginehighcheap, fast
Testcontainershigh — the real enginehigh — fresh per runneeds Docker, slower
Shared test serverhighlow — shared state, contentionbrittle, hard to reset

Testcontainers hits the sweet spot for integration tests: the real engine (catching dialect/behavior differences H2 misses) with per-run isolation (unlike a shared server).

Trap — these tests need a Docker daemon

Testcontainers can't run without Docker. That's exactly why this course's infra-heavy labs are excluded from the default mvn test (Part B lab policy) and run only where Docker is available (your machine, or a CI runner with Docker). Don't put a Testcontainers test in the always-green default suite.

Gotcha — reuse images, don't pull every test

Pin image tags (postgres:16, not latest) for reproducibility, and let Testcontainers reuse the pulled image and (optionally) a shared container across a class. Starting a fresh container per test method is slow; per class or per suite is usually the right granularity.

SDET note

This is where Module 16 (test databases) and Module 20 meet. In CI you'd run H2-backed unit tests in the fast PR build, and Testcontainers-backed integration tests (tagged slow/integration) in the nightly full build (next lesson). The lab's TestSelector models exactly that split.

Key Takeaways

  • A Docker image is a reproducible package; a container is a running instance — same behavior everywhere.
  • Testcontainers starts/stops real dependencies (Postgres, Kafka, …) within the test lifecycle, on random ports, torn down afterward.
  • It beats mocks (real engine) and shared servers (per-run isolation) for integration tests.
  • These tests require Docker, so they're excluded from the always-green default run.
  • Pin image tags and reuse containers per class/suite, not per method.

Lesson Quiz

Lesson Quiz · Docker & Testcontainers0 / 5
  1. A Docker image vs a container:

    • AThey're the same thing
    • BAn image is the immutable package; a container is a running instance of it
    • CA container is smaller than an image
    • DAn image runs; a container is stored
  2. What does Testcontainers do?

    • AMocks Java objects
    • BStarts and stops real dependencies in throwaway Docker containers, managed by the test lifecycle
    • CReplaces JUnit
    • DCaches Maven dependencies
  3. Why does Testcontainers expose a random mapped port?

    • AFor security only
    • BSo parallel/concurrent test runs don't collide on a fixed port
    • CBecause Postgres requires it
    • DTo slow tests down
  4. Compared with in-memory H2, Testcontainers offers…

    • ALower fidelity
    • BHigher fidelity — the real database engine, catching dialect/behavior differences
    • CNo isolation
    • DNo need for Docker
  5. Why are Testcontainers tests excluded from this course's default mvn test?

    • AThey're too easy
    • BThey require a Docker daemon, which isn't guaranteed in the default pure-Java run
    • CThey don't compile
    • DJUnit can't run them

Next: Reports, Artifacts & Test Selection. This module's lab is in labs/src/main/java/com/jse21/m20_cicd/.