Spring Test, MockMvc, WebTestClient und Testcontainers
Spring Test und Testcontainers ermöglichen Unit-, Slice-, Integration- und End-to-End-nahe Tests mit realen Infrastrukturabhängigkeiten.
Fachliche Einordnung
Enterprise-Tests müssen schnell und glaubwürdig sein. Mocks allein finden keine SQL-, Mapping-, Security- oder Messaging-Probleme; komplette End-to-End-Tests allein sind zu langsam.
Enterprise-Merksatz: Spring Test und Testcontainers ermöglichen Unit-, Slice-, Integration- und End-to-End-nahe Tests mit realen Infrastrukturabhängigkeiten.
Technische Darstellung
Kernkonzepte
- @SpringBootTest für vollständigen Kontext.
- @WebMvcTest, @DataJpaTest und andere Slices.
- MockMvc für MVC, WebTestClient für reactive und auch Server-Tests.
- Testcontainers für echte Datenbanken, Kafka, RabbitMQ usw.
- Testpyramide plus Contract Tests.
Wann einsetzen?
- Du willst DB- und Messaging-Verhalten realistisch prüfen.
- Du möchtest API-Verhalten ohne echten Server testen.
- Du brauchst reproduzierbare lokale Infrastrukturtests.
Typische Fehler und Risiken
- Jeder Test startet kompletten Kontext.
- Testcontainers Images nicht versionieren.
- Zu viele Mocks verdecken Integrationsfehler.
Legacy- und Modernisierungssicht
Vor Modernisierung werden Legacy-Charakterisierungstests ergänzt; neue Spring-Adapter erhalten Container-Integrationstests gegen echte Infrastruktur.
Ausführliches Beispiel
Das Beispiel zeigt bewusst nicht nur Annotationen, sondern auch die Verantwortung der Schicht. In echten Projekten sollte der technische Spring-Code an Adapter- oder Konfigurationsrändern bleiben, während die Fachlogik testbar und möglichst frameworkarm bleibt.
@SpringBootTest
@Testcontainers
class OrderRepositoryIntegrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:17-alpine");
@DynamicPropertySource
static void datasource(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Test
void storesOrder() {
OrderEntity saved = repository.save(OrderEntity.draft("C-100"));
assertThat(repository.findById(saved.id())).isPresent();
}
}
Checkliste für Reviews
- Ist die Verantwortung des Bausteins klar: Framework steuert Lebenszyklus, Library wird gezielt benutzt?
- Ist die Fachlogik außerhalb von Controller, Listener, Repository-Implementierung oder Konfiguration?
- Sind Fehlerfälle, Timeouts, Security, Monitoring und Tests sichtbar modelliert?
- Gibt es klare Grenzen zwischen DTO, Domäne, Persistence und Infrastruktur?