Nie wieder 'Works on my Machine': Datenbank-Tests richtig gemacht

Julian | Dec 20, 2025 min read

“Aber lokal lief alles grün!”

Wir kennen alle diesen Schmerz: Deine Pipeline ist grün, deine Tests laufen lokal in Millisekunden durch. Du deployst auf Staging oder Production – und es knallt. SQLSyntaxErrorException. Oder schlimmer: Daten werden falsch gespeichert.

Der Grund? Deine Tests laufen gegen eine H2 In-Memory Datenbank, aber deine App läuft gegen eine echte PostgreSQL (oder MySQL/Oracle).

Jahrelang haben wir uns eingeredet: “Ach, H2 verhält sich ja fast wie Postgres.” Spoiler: Tut es nicht. Und als Senior Engineer sage ich dir: Hör auf, deine Datenbank zu mocken. Teste gegen das echte Ding.

Hier kommt Testcontainers ins Spiel.

Das Problem mit der “Fake”-Datenbank

Warum nutzen wir überhaupt H2? Weil es bequem ist. Keine Installation, super schnell, startet mit der App. Aber der Preis, den du zahlst, ist False Confidence (falsches Vertrauen).

  • Syntax-Unterschiede: H2 unterstützt nicht alle JSONB-Funktionen von Postgres.
  • Dialekte: Ein Query, der in H2 funktioniert, kann in Postgres crashen (und umgekehrt).
  • Constraints: Trigger und Stored Procedures verhalten sich oft anders.

Wenn du H2 nutzt, testest du nicht deine Datenbank-Logik. Du testest, ob Hibernate funktioniert. Das ist zu wenig.

Die Lösung: Docker im JUnit-Test

Testcontainers ist eine Java-Library, die es dir erlaubt, Docker-Container direkt aus deinem JUnit-Test heraus zu starten. Wenn du deine Tests startest, passiert Folgendes:

  1. Java fährt eine echte PostgreSQL-Instanz in Docker hoch.
  2. Deine Spring Boot App verbindet sich dynamisch gegen diesen Container.
  3. Die Tests laufen gegen die echte DB.
  4. Nach dem Test wird der Container vernichtet.

Kein manuelles docker-compose up vor dem Testen. Keine “leaky” Daten von vorherigen Testläufen. Jedes Mal eine frische, echte Umgebung.

Die Umsetzung (Der moderne Weg mit Spring Boot 3.1+)

Früher war Testcontainers etwas fummelig zu konfigurieren (dynamische Ports manuell setzen etc.). Seit Spring Boot 3.1 ist es fast magisch dank @ServiceConnection.

Hier ist der Code, den du heute schreiben solltest.

Schritt 1: Dependencies (Maven)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-testcontainers</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <scope>test</scope>
</dependency>

Schritt 2: Der Test-Setup Vergiss komplizierte application-test.yml Dateien. Wir definieren den Container direkt im Test:

@SpringBootTest
@Testcontainers
class OrderIntegrationTest {

    // Wir definieren: Wir wollen ein echtes Postgres 15 Image
    @Container
    @ServiceConnection // <- Die Magie: Spring injiziert URL/User/Passwort automatisch!
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine");

    @Autowired
    private OrderRepository orderRepository;

    @Test
    void shouldSaveAndFindOrder() {
        Order order = new Order("MacBook Pro", 9999.00);
        orderRepository.save(order);

        var foundOrder = orderRepository.findByName("MacBook Pro");
        
        assertThat(foundOrder).isPresent();
        // Dieser Test lief gegen eine echte DB!
    }
}

Das war’s. Spring Boot erkennt den Container, sieht die @ServiceConnection Annotation und überschreibt automatisch die DataSource-Properties. Du musst dich um keine Ports oder Passwörter kümmern.

Ein Wort zur Geschwindigkeit

“Aber Julian, das dauert doch viel länger!”

Ja, das Starten eines Docker-Containers dauert 2-3 Sekunden. H2 dauert 200 Millisekunden. Aber Integrationstests sind nicht dazu da, um während des Tippens ausgeführt zu werden (dafür hast du Unit Tests). Integrationstests sind dein Sicherheitsnetz.

Und mal ehrlich: Was kostet dich mehr Zeit? A) 5 Sekunden länger auf die Tests warten? B) 5 Stunden Debugging, weil der SQL-Query in Production fehlschlägt, obwohl der Test grün war?

Qualität schlägt Geschwindigkeit. Immer.

Fazit: Realität statt Simulation

Mit Testcontainers bringst du Production Parity in deine Entwicklungsumgebung. Du eliminierst eine komplette Klasse von Fehlern (“It works on H2”).

Es funktioniert übrigens nicht nur für Datenbanken. Du kannst Redis, Kafka, Elasticsearch oder sogar LocalStack (AWS) als Container hochfahren.

Mein Rat: Verbanne H2 aus deinen Integrationstests. Deine Nerven (und deine User) werden es dir danken.

Happy Testing! 🧪