Skip to content
SDETRole
SDETRole

Docker interview question

What is Testcontainers and when would you use it instead of a shared test database or mocks?

advanced 5+ yrs #testcontainers#integration-testing#java

Short answer

Testcontainers is a library that starts real dependencies such as Postgres, Kafka or Redis in throwaway Docker containers from test code, with wait strategies and random ports. Use it for integration tests that need real behaviour; trade-offs are Docker availability in CI, startup cost and container reuse strategy.

Answer

Testcontainers is an open-source library (Java originally, with ports for .NET, Go, Node, Python and others) that lets tests start real dependencies in Docker containers programmatically, wait until they are ready, and remove them afterwards. Instead of pointing integration tests at a shared database or replacing it with H2, you test against the same Postgres major version you run in production.

Example with JUnit 5

The imports below follow the long-standing Testcontainers for Java 1.x layout; if your project is on a newer major version, check the module’s current package names.

import org.junit.jupiter.api.Test;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;

import static org.junit.jupiter.api.Assertions.assertTrue;

@Testcontainers
class OrderRepositoryIT {

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

    @Test
    void connectsToRealPostgres() throws Exception {
        try (Connection conn = DriverManager.getConnection(
                postgres.getJdbcUrl(), postgres.getUsername(), postgres.getPassword());
             ResultSet rs = conn.createStatement().executeQuery("select version()")) {
            assertTrue(rs.next());
            assertTrue(rs.getString(1).contains("PostgreSQL"));
        }
    }
}

The static field means one container per test class; a non-static field would start a container per test method. The container is mapped to a random host port, so parallel builds on one agent do not collide, and the module’s default wait strategy blocks until Postgres accepts connections. With Spring Boot, @ServiceConnection (Boot 3.1+) or @DynamicPropertySource wires the URL into the application context.

When to use it

  • Repository and migration tests where SQL dialect, constraints, JSON columns or locking behaviour matter. H2 in “Postgres mode” is not Postgres.
  • Messaging: real Kafka or RabbitMQ to verify serialisation, consumer groups, retries and dead-letter handling.
  • Service-level component tests: the service runs against real infrastructure, while external HTTP APIs are stubbed (WireMock also has a Testcontainers module).

When not to

  • Pure business logic: unit tests with no infrastructure are faster and clearer.
  • Third-party SaaS that you cannot run locally: stub it or use contract tests.
  • Full end-to-end flows across many services: that belongs in a deployed environment.

Trade-offs and how to manage them

  • Docker is required on developer machines and CI agents (or a remote Docker host / Testcontainers Cloud). Docker-in-Docker on CI needs configuration.
  • Startup cost. Starting a container per test class is seconds each. Common mitigations: a singleton container started once in a base class or extension and shared across classes, and keeping data isolation at the data level.
  • Isolation inside a shared container. Truncate tables between tests, wrap tests in rolled-back transactions where the framework allows, or use unique identifiers per test.
  • Image versions. Pin the image tag to match production and update it deliberately.
  • Local reuse (withReuse(true) plus an opt-in property) speeds up local iteration but should stay off in CI.

What interviewers look for

  • What Testcontainers does: real dependencies, lifecycle, random ports, wait strategies.
  • Why it beats H2 or mocks for integration tests, and where it does not belong.
  • Practical concerns: Docker in CI, startup time, singleton pattern and data isolation.

A strong answer covers

  • Library that manages real dependencies in Docker containers from test code
  • Lifecycle tied to tests, random ports, wait strategies
  • Higher fidelity than mocks or in-memory databases like H2
  • Trade-offs: needs Docker in CI, startup time, shared or singleton containers
  • Data isolation between tests: truncate, transactions or unique data
Practise answering under time pressure

Type to search across SDETRole.

Full-text search by Pagefind · runs in your browser Open search page →