PATTERN Cited by 1 source
Real Docker container over in-memory fake¶
Pattern¶
For integration tests that cross a system boundary (database, AWS service, HTTP peer, queue, cache), run the real server as a Docker container via systems/testcontainers rather than an in-memory emulator (H2, embedded-Kafka, Redis-java, a hand-rolled fake).
Trade: pay the container startup cost (seconds) to get real engine semantics. Amortise the cost with the singleton container pattern.
Why¶
Emulators approximate their reference engine's SQL / protocol / error-codes but diverge on:
- Engine-specific features — Postgres
jsonboperators,LATERALjoins, declarative partitioning, PL/pgSQL stored procedures, full-text search, logical replication. H2 either doesn't implement these or implements them slightly wrong. See concepts/h2-vs-real-database-testing. - Error shapes — SQLSTATE codes, exception classes, constraint violation messages. Tests that pattern-match on these silently drift.
- Behaviour under load / concurrency — lock acquisition, MVCC snapshot timing, queue fairness. In-memory fakes are single-threaded shortcuts.
- Wire protocol edge cases — for HTTP peers, a MockServer or WireMock container lets you simulate exactly the HTTP codes, delays, and connection-drops that your application has to handle.
- AWS service semantics — S3 eventual consistency windows, DynamoDB provisioned-capacity throttles, Kinesis partition key behaviour. systems/localstack is imperfect but closer to AWS than any hand-rolled fake; and it runs offline.
Mechanism¶
From Zalando ZMS (sources/2021-02-24-zalando-integration-tests-with-testcontainers):
- Declare the container in test code:
new PostgreSQLContainer("postgres:13.1")with credentials. - Start it once per JVM (
static { container.start(); }on a shared base class — see patterns/shared-static-container-across-tests). - Wire dynamic ports / credentials into the application context
via Spring's
@DynamicPropertySource(5.2.5+). - Tests run against the real Postgres. Isolation is maintained by unique IDs or per-test cleanup.
- Rely on JVM shutdown hooks + Testcontainers'
Ryuk reaper for
cleanup; no explicit
.stop()needed.
Cost¶
- Startup latency: ~4 s Postgres, ~0.4 s H2, up to ~20 s Localstack on Zalando's author machine. Singleton pattern makes this a per-JVM tax, not a per-test tax.
- Docker daemon requirement — devs and CI workers need a Docker-compatible runtime (Docker Desktop, Colima, Podman, rootless Docker).
- Memory / CPU — bigger CI instances, especially for Localstack or multi-container suites.
When not to use¶
- Pure unit tests that never execute SQL / HTTP — use a real unit test with mocks instead.
- Fast developer inner loops — the
mvn test(Surefire) pass should stay under seconds. Put Testcontainers-backed tests behind patterns/failsafe-integration-test-separation so they only run on CI and explicitmvn verify -P with-integration-tests. - Test counts so high that even singleton-shared Docker is the bottleneck — at that point the pyramid shape (concepts/test-pyramid) is wrong and too many ITs exist relative to units.
What it still doesn't cover¶
- Real-API drift. A MockServer/WireMock container tests that your code works against that mock, not against the real external peer, which may have changed. Pair with concepts/contract-testing.
- Geographic / network-latency effects. Local container latency is ~100 μs; real AWS is ~10–100 ms.
- Full-stack interactions. System / E2E tests against the deployed stack remain necessary but rare.
Seen in¶
- sources/2021-02-24-zalando-integration-tests-with-testcontainers — Zalando ZMS's explicit case for Postgres-in-Docker over H2 for Java backend tests. Names all three major use cases: DB (real Postgres), AWS (Localstack), HTTP peers (MockServer / WireMock).
Related¶
- systems/testcontainers · systems/postgresql · systems/localstack · systems/mockserver · systems/wiremock
- concepts/h2-vs-real-database-testing — the canonical antipattern this pattern replaces.
- concepts/singleton-container-pattern — the startup-cost amortisation mechanism.
- concepts/test-pyramid — the discipline frame this pattern lives inside.
- patterns/shared-static-container-across-tests — the implementation form.
- patterns/failsafe-integration-test-separation — the Maven plumbing that makes the pattern compatible with fast local feedback.