Wer Integration-Tests schreibt, kennt das Dilemma: Mocks sind schnell, aber unrealistisch – grüne Tests bedeuten nicht automatisch, dass der Code mit echten Systemen funktioniert. Umgekehrt sind geteilte Testumgebungen oft langsam, schwer wartbar und beeinflussen sich gegenseitig.
TestContainers löst diesen Zielkonflikt pragmatisch: Echte Dienste (z. B. PostgreSQL, Kafka, Redis) werden pro Test in wegwerfbaren Docker-Containern gestartet – isoliert, reproduzierbar und automatisch aufgeräumt.
In diesem Leitfaden erfahren Sie:
- Was TestContainers ist und wie es funktioniert
- Wann „Container pro Test“ sinnvoll ist und wann Shared Containers
- Best Practices (Wait Strategies, Module, eigene Wrapper)
- Unterschiede zwischen Implementierungen (Java, Go, PHP …)
- Ein Praxisbeispiel mit EventSourcingDB
Was ist TestContainers?
TestContainers ist eine Bibliothek, die es erlaubt, Docker-Container programmatisch aus Tests heraus zu starten, zu überwachen und wieder zu beenden. Anstatt Mocks zu schreiben oder separate Testumgebungen zu pflegen, startet der Test die benötigte Infrastruktur selbst.
Grundprinzip:
- Container konfigurieren und starten
- Warten, bis der Dienst „ready“ ist (Wait Strategy)
- Testschritte ausführen
- Container automatisch aufräumen (Reaper/Shutdown-Hook)
Vorteile auf einen Blick
- Realitätsnah: Tests laufen gegen echte Dienste
- Isoliert: Jeder Test hat seinen eigenen Zustand
- Schnell & reproduzierbar: Wegwerf-Container, keine „Umgebungs-Schulden“
- Dev- & CI‑freundlich: Gleiches Verhalten lokal und in der Pipeline
Kernkonzepte, die man beherrschen sollte
Dynamische Ports & Netzwerke
TestContainers mappt Container-Ports dynamisch auf freie Host-Ports, sodass es keine Kollisionen zwischen Tests gibt. Netzwerke lassen sich pro Test definieren, um Topologien realistisch abzubilden.
Reaper & Cleanup
Ein integrierter Reaper-Mechanismus sorgt dafür, dass Container auch bei Testabbruch entfernt werden. So bleiben keine „Zombie“-Container oder Volumes zurück.
Wait Strategies (Wartebedingungen)
Ein gestarteter Container ist nicht sofort ansprechbar. Wait Strategies definieren, wann ein Dienst wirklich bereit ist:
- Port lauscht
- HTTP-Antwort mit Status 200/OK
- Log-Zeile erscheint („Ready to accept connections“)
- Healthcheck erfolgreich
Best Practice: Verwenden Sie dienstspezifische Wait Strategies. Eine DB, die Ports bindet, ist nicht zwingend migrationsbereit.
Vorgefertigte Module (schnell zum Ziel)
Für gängige Systeme gibt es Module mit sinnvollen Defaults (Ports, Env-Variablen, Waits):
- PostgreSQL, MySQL/MariaDB
- MongoDB, Redis
- Kafka, RabbitMQ
- (je nach Sprache weitere)
Diese Module verkürzen die Einrichtung und sind ideal, wenn Sie schnell starten möchten.
„Container pro Test“ oder „Shared Containers“?
- Container pro Test
- Maximale Isolation & Reproduzierbarkeit
- Ideal, wenn der Dienst < 1 s bis „ready“ benötigt
− Mehr Start-Overhead bei schweren Images
- Shared Containers (pro Testklasse/Suite)
- Sinnvoll für langsam startende Dienste
- Weniger Ressourcenverbrauch
− Sorgfältige State-Rücksetzung notwendig (DB-Reset, Topics leeren etc.)
Daumenregel: Wenn der Dienst schnell genug bereit ist (z. B. Redis, kleine DBs), bevorzugen Sie „Container pro Test“.
TestContainers vs. Docker Compose
| Aspekt | TestContainers (programmatisch) | Docker Compose (YAML, deklarativ) |
|---|---|---|
| Isolation | Pro Test / Suite, dynamisch | Oft geteilte Umgebung |
| Steuerung | Code (pro Test anpassbar) | Statisches Setup per YAML |
| Cleanup | Automatisch (Reaper) | Manuell/skriptbasiert |
| Parallelisierung | Gut (dynamische Ports) | Heikler (Portkollisionen möglich) |
| Use Case | Integration-Tests | Lokale Dev-Stacks, manuelle Tests |
Beides hat seine Berechtigung – für automatisierte Integration-Tests ist TestContainers jedoch meist die robustere Wahl.
Mehr Idee als monolithisches Projekt: Sprach-Ökosysteme
TestContainers ist eine Idee, die in vielen Sprachen umgesetzt wurde: Java, Go, .NET, Node.js, Python, PHP, Rust u. a.
Konsequenz: APIs und Features unterscheiden sich. Was in Java selbstverständlich ist, kann in Python fehlen – oder umgekehrt. Lesen Sie die Doku Ihrer Implementierung und verlassen Sie sich nicht auf Feature-Parität.
Praktischer Tipp: Erstellen Sie interne Guidelines („So nutzen wir TestContainers in unserer Sprache“) mit Beispielen, Wait-Strategien und Cleanup-Regeln.
Praxisbeispiel: EventSourcingDB
EventSourcingDB (von the native web) ist eine auf Event Sourcing spezialisierte Datenbank. Für Tests ist sie interessant, weil sie in < 1 Sekunde startet – perfekt für den Ansatz „Container pro Test“.
Die Client-SDKs (u. a. Go, PHP) bringen eigene TestContainers-Integrationen mit. Das zeigt exemplarisch, wie gleiche Konzepte in verschiedenen Sprachen ähnlich, aber nicht identisch umgesetzt werden.
Lektion: Wählen Sie bewusst Dienste, die schnell starten und ein klares Readiness-Signal liefern. Das erhöht Testgeschwindigkeit und -stabilität.
Best Practices (Kurz & knackig)
- Schnelle Images wählen (alpine, schlanke Dienste)
- Explizite Wait Strategies definieren (Logs/HTTP/Ports/Healthcheck)
- Module nutzen, wo verfügbar (Postgres/Kafka/Redis etc.)
- Eigene Wrapper für interne Systeme schreiben
- Testdaten deterministisch initialisieren (Migrations, Seeds, Fixtures)
- Parallelisierung erlauben (dynamische Ports, separate Netzwerke)
- Ressourcen im Blick behalten (CPU/RAM/IO, Limits für CI)
- Secrets sicher injizieren (Env, aber keine Hardcodings in Git)
- CI-ready machen (Docker in der Pipeline, Caching für Images)
- Flaky-Tests eliminieren (stabile Readiness-Signale, Timeouts justieren)
Typische Stolperfallen (und wie man sie vermeidet)
- Port lauscht ≠ Dienst bereit → Dienstspezifische Waits verwenden
- Langsamer Dienst → Shared Container + State-Reset pro Test
- Nicht deterministische Seeds → Feste Fixtures, Idempotenz
- Versteckte Abhängigkeiten (Zeit/NTP, DNS, Locale) → Container klar konfigurieren
- „Es lief lokal!“ → Gleiches Docker-Setup lokal & in CI (Versionen pinnen)
Einsatz in CI/CD
- Docker muss in der Pipeline verfügbar sein (Docker‑in‑Docker oder Remote‑Daemon)
- Image-Caching nutzen (Registry, BuildKit)
- Ressourcenlimitierung (max. parallele Container)
- Test-Matrix (verschiedene DB‑Versionen) für Kompatibilitätstests
- Logs & Artefakte bei Fehlschlägen sichern (zur Fehlersuche)
FAQ
Brauche ich Docker?
Ja. TestContainers orchestriert Docker-Container – lokal und in CI.
Sind Mocks damit überflüssig?
Nein. Unit-Tests bleiben mit Mocks sinnvoll. TestContainers adressiert Integration-Tests.
Wie gehe ich mit langsamen Diensten um?
Shared Container pro Suite + konsequenter State-Reset und klare Isolationsregeln.
Kann ich mehrere Dienste kombinieren?
Ja. Orchestrieren Sie z. B. PostgreSQL + Kafka + Redis im selben Testnetzwerk.
Gibt es Feature-Parität zwischen Sprachen?
Nein. Doku lesen, Beispiele der jeweiligen Implementierung prüfen.
Fazit
TestContainers schließt die Lücke zwischen schnellen, aber unrealistischen Mocks und realistischen, aber spröden Testumgebungen. Mit ephemeren, isolierten Containern pro Test erhalten Sie verlässliche, reproduzierbare Integration-Tests – lokal wie in CI/CD.
Der Schlüssel zum Erfolg: schnelle Images, präzise Wait Strategies, saubere Testdaten und klare Isolations- und Cleanup-Regeln.