Woche 7, Tag 1 — Spring Boot Testing Mental Model
Ziel
Heute verstehst du das große Bild beim Testen in Spring Boot.
Die Kernfragen:
- Warum schreiben wir Tests?
- Was ist die Testpyramide?
- Was ist ein Unit-Test?
- Was ist ein Slice-Test?
- Was ist ein Integrationstest?
- Was ist der Spring TestContext?
- Warum sind Spring-Tests langsamer als reine Unit-Tests?
- Was macht
@SpringBootTest? - Was sind Test-Slices wie
@WebMvcTestund@DataJpaTest? - Wann solltest du Mocks verwenden?
- Wann solltest du den echten Spring-Context verwenden?
- Was sind häufige Prüfungsfallen?
1. Kurz-Wiederholung aus Woche 6
In Woche 6 hast du gelernt:
- Spring Security schützt Anfragen, bevor sie Controller erreichen.
- Authentifizierung bedeutet, die Identität nachzuweisen.
- Autorisierung bedeutet, Berechtigungen zu prüfen.
- 401 bedeutet: nicht authentifiziert.
- 403 bedeutet: authentifiziert, aber nicht berechtigt.
SecurityFilterChaindefiniert Sicherheitsregeln.UserDetailsServicelädt Benutzer.PasswordEncoderhasht Passwörter.@PreAuthorizeschützt Methoden.- CSRF und CORS sind unterschiedlich.
- Sicherheitstests sollten erlaubte und verweigerte Fälle prüfen.
Merksatz:
Sicherheitstests beweisen, dass zugelassene Benutzer zugelassen und verbotene Benutzer blockiert werden.
Jetzt gehst du zu einem größeren Thema über:
Wie teste ich eine Spring-Boot-Anwendung gut?
2. Warum schreiben wir Tests?
Tests helfen, diese Fragen zu beantworten:
Funktioniert mein Code?
Funktioniert er nach Änderungen noch?
Habe ich ein altes Feature kaputt gemacht?
Gibt mein Controller die richtige Antwort zurück?
Hält sich mein Service an die Geschäftsregeln?
Fragt mein Repository die Datenbank korrekt ab?
Blockiert die Sicherheit die falschen Benutzer?
Lehnt die Validierung ungültige Eingaben ab?
Tests dienen nicht nur der Code-Prüfung.
Sie sind auch Dokumentation.
Ein guter Test sagt:
So soll sich die Anwendung verhalten.
Merksatz:
Tests schützen das Verhalten.
3. Was sollten wir testen?
In einer Spring-Boot-App testest du oft:
Geschäftslogik
Controller
Request-Validierung
Exception Handling
Repositories
Datenbankabfragen
Transaktionen
Sicherheitsregeln
JSON-Serialisierung
externe API-Clients
Konfiguration
vollständigen Anwendungsfluss
Aber du solltest nicht alles auf dieselbe Weise testen.
Manche Dinge brauchen kleine, schnelle Tests.
Manche brauchen den Spring-Context.
Manche brauchen eine echte Datenbank.
Merksatz:
Unterschiedliche Probleme brauchen unterschiedliche Testtypen.
4. Die Testpyramide
Die Testpyramide ist ein mentales Modell.
E2E Tests
/ \
/ Integration \
/ Tests \
/ \
/ Slice Tests \
/ \
/ Unit Tests \
/_________________________\
Bedeutung:
viele Unit-Tests
einige Slice-/Integrationstests
wenige End-to-End-Tests
Warum?
Unit-Tests sind schnell
Integrationstests sind langsamer
End-to-End-Tests sind am langsamsten und fragiler
Merksatz:
Viele schnelle Tests, weniger langsame Vollsystem-Tests.
5. Wichtige Testtypen
| Testtyp | Was getestet wird | Spring-Context? | Geschwindigkeit |
|---|---|---|---|
| Unit-Test | eine Klasse/Methode | nein | sehr schnell |
| Slice-Test | eine Spring-Schicht | teilweise | mittel |
| Integrationstest | mehrere Schichten/ganze App | ja | langsamer |
| End-to-End-Test | echter Nutzerfluss | oft ganzes System | am langsamsten |
Merksatz:
Unit-Tests sind klein. Integrationstests sind realistisch.
6. Unit-Test
Ein Unit-Test prüft ein kleines Stück Code.
Üblicherweise:
kein Spring-Context
keine Datenbank
kein Webserver
kein echtes HTTP
keine echte externe API
Beispiel-Ziele:
TaskService-Geschäftslogik
PriceCalculator
PermissionService
Validator-Helfer
Mapper
Merksatz:
Ein Unit-Test prüft eine Einheit, ohne Spring zu starten.
7. Unit-Test-Beispiel
Service:
public class TaskTitlePolicy {
public void validate(String title) {
if (title == null || title.isBlank()) {
throw new IllegalArgumentException("Title must not be blank");
}
if (title.length() > 100) {
throw new IllegalArgumentException("Title is too long");
}
}
}
Unit-Test:
class TaskTitlePolicyTest {
private final TaskTitlePolicy policy = new TaskTitlePolicy();
@Test
void rejectsBlankTitle() {
assertThatThrownBy(() -> policy.validate(" "))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Title must not be blank");
}
@Test
void acceptsValidTitle() {
policy.validate("Prepare tax documents");
}
}
Kein Spring.
Keine Datenbank.
Sehr schnell.
Merksatz:
Wenn du ohne Spring testen kannst, teste ohne Spring.
8. Unit-Test mit Mockito
Service:
public class TaskService {
private final TaskRepository taskRepository;
public TaskService(TaskRepository taskRepository) {
this.taskRepository = taskRepository;
}
public TaskDto findById(Long id) {
TaskEntity task = taskRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Task", id));
return new TaskDto(task.getId(), task.getTitle(), task.getStatus());
}
}
Unit-Test:
class TaskServiceTest {
private final TaskRepository taskRepository = mock(TaskRepository.class);
private final TaskService taskService = new TaskService(taskRepository);
@Test
void returnsTaskById() {
TaskEntity task = new TaskEntity(1L, "Learn tests", "OPEN");
when(taskRepository.findById(1L))
.thenReturn(Optional.of(task));
TaskDto result = taskService.findById(1L);
assertThat(result.id()).isEqualTo(1L);
assertThat(result.title()).isEqualTo("Learn tests");
assertThat(result.status()).isEqualTo("OPEN");
}
}
Dieser Test prüft das Service-Verhalten.
Er prüft nicht, ob die Repository-Abfrage wirklich funktioniert.
Merksatz:
Mocke Abhängigkeiten, wenn du eine Klasse isoliert testest.
9. Wofür Unit-Tests gut sind
Unit-Tests sind gut für:
Geschäftsregeln
if/else-Logik
Berechnungen
Mapping-Logik
Berechtigungslogik
Validierungs-Helferlogik
Fehlerverhalten
Randfälle
Beispiele:
Darf der Benutzer auf die Aufgabe zugreifen?
Ist die Rechnung überfällig?
Kann die Aufgabe abgeschlossen werden?
Ist das Passwort stark genug?
Soll eine Benachrichtigung gesendet werden?
Merksatz:
Unit-Tests sind am besten für Geschäftslogik und Randfälle.
10. Wofür Unit-Tests nicht ausreichen
Unit-Tests reichen nicht für:
JPA-Mapping
echte SQL-Abfragen
Spring-MVC-Request-Mapping
Jackson-JSON-Konvertierung
Bean-Validation-Integration
Spring-Security-Filterkette
Transaktionsverhalten
echte Anwendungskonfiguration
Dafür brauchst du Spring-Tests.
Merksatz:
Unit-Tests belegen nicht Spring-Wiring oder Framework-Integration.
11. Slice-Test
Ein Slice-Test lädt nur einen Teil der Spring-Anwendung.
Beispiele:
@WebMvcTest
@DataJpaTest
@JsonTest
@RestClientTest
Slice-Tests sind schneller als vollständige Integrationstests, weil sie nicht die ganze Anwendung laden.
Sie testen eine Schicht oder einen technischen Slice.
Merksatz:
Ein Slice-Test lädt nur den Teil von Spring, den diese Schicht braucht.
12. Häufige Slice-Tests
| Annotation | Testet |
|---|---|
@WebMvcTest | Controller, MVC, Validierung, JSON, MockMvc |
@DataJpaTest | JPA-Entities, Repositories, Abfragen |
@JsonTest | JSON-Serialisierung/Deserialisierung |
@RestClientTest | REST-Clients |
@JdbcTest | JDBC-Komponenten |
Merksatz:
Slice-Annotationen fokussieren den Spring-Context.
13. @WebMvcTest
@WebMvcTest ist zum Testen der Web-Schicht gedacht.
Es testet üblicherweise:
Controller-Request-Mapping
Request-Parameter
Request-Body-JSON
Validierung
HTTP-Status
Response-JSON
Controller-Exception-Handling
Sicherheitsregeln, falls eingebunden
Beispiel:
@WebMvcTest(TaskController.class)
class TaskControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private TaskService taskService;
@Test
void returnsTask() throws Exception {
when(taskService.findById(1L))
.thenReturn(new TaskDto(1L, "Learn MVC test", "OPEN"));
mockMvc.perform(get("/api/tasks/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").value(1))
.andExpect(jsonPath("$.title").value("Learn MVC test"));
}
}
Wichtig:
Der Service ist gemockt.
Der Controller wird getestet.
Merksatz:
@WebMvcTesttestet die Controller-Schicht, nicht den echten Service oder die Datenbank.
14. @DataJpaTest
@DataJpaTest ist zum Testen der JPA-Persistenz gedacht.
Es testet üblicherweise:
Entity-Mapping
Repository-Methoden
abgeleitete Abfragen
@Query-Methoden
Beziehungen
Constraints
Paginierung
Sortierung
Datenbankverhalten
Beispiel:
@DataJpaTest
class TaskRepositoryTest {
@Autowired
private TaskRepository taskRepository;
@Autowired
private TestEntityManager entityManager;
@Test
void findsByStatus() {
entityManager.persist(new TaskEntity("Task A", "OPEN"));
entityManager.persist(new TaskEntity("Task B", "DONE"));
entityManager.flush();
List<TaskEntity> result = taskRepository.findByStatus("OPEN");
assertThat(result)
.extracting(TaskEntity::getTitle)
.containsExactly("Task A");
}
}
Merksatz:
@DataJpaTesttestet Repositories und JPA-Mapping.
15. Mentales Modell für Slice-Tests
Ein Slice-Test fragt:
Funktioniert diese Spring-Schicht korrekt?
Beispiele:
Parst der Controller JSON korrekt?
Gibt die Validierung 400 zurück?
Gibt der Exception Handler eine Fehlerantwort zurück?
Liefert die Repository-Abfrage die richtigen Zeilen?
Serialisiert Jackson dieses DTO korrekt?
Er fragt nicht:
Funktioniert die ganze Anwendung End-to-End?
Merksatz:
Slice-Tests sind fokussierte Spring-Tests.
16. Integrationstest
Ein Integrationstest prüft, ob mehrere Teile zusammenarbeiten.
Beispiel:
Controller + Service + Repository + Datenbank
oder:
Security + Controller + Service
oder:
Application Context + Konfiguration + externer Client-Mock
Integrationstests verwenden oft:
@SpringBootTest
Merksatz:
Integrationstests belegen, dass mehrere Teile zusammenarbeiten.
17. @SpringBootTest
@SpringBootTest lädt den vollständigen Spring-Boot-Application-Context.
Beispiel:
@SpringBootTest
class ApplicationContextTest {
@Test
void contextLoads() {
}
}
Das prüft:
Kann der Application Context starten?
Sind Beans korrekt konfiguriert?
Sind Abhängigkeiten korrekt verdrahtet?
Ist die Konfiguration gültig?
Aber es kann langsamer sein.
Merksatz:
@SpringBootTeststartet den vollständigen Application Context.
18. @SpringBootTest mit MockMvc
Beispiel:
@SpringBootTest
@AutoConfigureMockMvc
class TaskApiIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Test
void getsTask() throws Exception {
mockMvc.perform(get("/api/tasks/1"))
.andExpect(status().isOk());
}
}
Das kann testen:
vollständigen Application Context
Security-Filter
Controller
Services
Repositories
Datenbank
Exception Handling
JSON-Konvertierung
Merksatz:
@SpringBootTest + MockMvctestet den Web-Flow ohne einen echten Server zu starten.
19. @SpringBootTest Web Environment
@SpringBootTest kann verschiedene Web-Umgebungen nutzen.
Gängige Optionen:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
Bedeutung:
| Modus | Bedeutung |
|---|---|
MOCK | Mock-Servlet-Umgebung, kein echter Server |
RANDOM_PORT | startet echten Server auf zufälligem Port |
DEFINED_PORT | startet echten Server auf konfiguriertem Port |
NONE | keine Web-Umgebung |
Merksatz:
MOCKist üblich mit MockMvc;RANDOM_PORTstartet einen echten Server.
20. Integrationstest mit echtem HTTP
Beispiel:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class TaskApiRealHttpTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
void getsHealth() {
ResponseEntity<String> response =
restTemplate.getForEntity("/actuator/health", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
}
Das startet einen echten eingebetteten Server.
Nützlich, wenn du testen willst:
echte HTTP-Schicht
Server-Port
Filter
Serialisierung
vollständiges Request/Response-Verhalten
Merksatz:
RANDOM_PORTtestet über echtes HTTP.
21. Was ist Spring TestContext?
Spring TestContext ist die Test-Framework-Unterstützung, die Spring Application Contexts in Tests verwaltet.
Es hilft bei:
Laden des Application Context
Dependency Injection in Tests
Transaktionsverwaltung in Tests
Test-Properties
aktiven Profilen
Context-Caching
DirtiesContext
Integration mit JUnit
Beispiel:
@SpringBootTest
class TaskServiceIntegrationTest {
@Autowired
private TaskService taskService;
}
Spring injiziert Beans in den Test, weil das TestContext-Framework aktiv ist.
Merksatz:
Spring TestContext verwaltet Spring Application Contexts für Tests.
22. Warum sind Spring-Tests langsamer?
Spring-Tests können langsamer sein, weil sie möglicherweise:
den Application Context starten
viele Beans erzeugen
Auto-Configuration laden
eine Datenbankverbindung aufbauen
eine eingebettete Web-Umgebung starten
Security-Filter initialisieren
Migrationen ausführen
den Classpath scannen
Unit-Tests vermeiden all das.
Merksatz:
Spring zu starten kostet Zeit — nutze es nur, wenn der Test Spring braucht.
23. Context-Caching
Spring kann Application Contexts zwischen Tests cachen.
Wenn zwei Tests dieselbe Context-Konfiguration nutzen, kann Spring den Context wiederverwenden.
Das macht Tests schneller.
Aber wenn du den Context änderst mit:
@DirtiesContext
dann kann Spring den Context schließen und neu aufbauen.
Merksatz:
Spring cached Test-Contexts — verschmutze den Context nicht unnötig.
24. @DirtiesContext
@DirtiesContext sagt Spring:
Dieser Test hat den Context geändert — verwende ihn nicht erneut.
Beispiel-Anwendungsfall:
Test ändert globalen Bean-Zustand
Test modifiziert den Application Context
Test ändert die Umgebung so, dass spätere Tests betroffen sind
Aber es verlangsamt die Test-Suite.
Merksatz:
Verwende
@DirtiesContextselten.
25. Test-Profile
Tests nutzen oft ein spezielles Profil.
Beispiel:
@ActiveProfiles("test")
@SpringBootTest
class TaskServiceIntegrationTest {
}
Dann kann Spring laden:
application-test.yml
Beispiel:
spring:
datasource:
url: jdbc:postgresql://localhost:5432/test_db
Merksatz:
Nutze ein Test-Profil für test-spezifische Konfiguration.
26. Test-Properties
Inline-Properties:
@SpringBootTest(properties = {
"feature.email.enabled=false",
"app.security.demo-mode=true"
})
class AppTest {
}
Das überschreibt Properties für den Test.
Nützlich für:
externe Integrationen deaktivieren
Feature-Flags ändern
Fake-URLs verwenden
Testverhalten konfigurieren
Merksatz:
Test-Properties passen den Application Context für Tests an.
27. @MockBean
@MockBean ersetzt einen Spring Bean im Application Context durch einen Mockito-Mock.
Beispiel:
@WebMvcTest(TaskController.class)
class TaskControllerTest {
@MockBean
private TaskService taskService;
}
Bedeutung:
Wenn der Controller TaskService braucht, injiziert Spring den Mock.
Nützlich für Slice-Tests.
Merksatz:
@MockBeanmockt einen Spring Bean innerhalb des Test-Contexts.
28. Mockito mock() vs. @MockBean
| Werkzeug | Wo es funktioniert |
|---|---|
mock() | einfacher Unit-Test |
@Mock | Mockito-Test |
@MockBean | Spring-Test-Context |
Beispiel einfacher Unit-Test:
TaskRepository repository = mock(TaskRepository.class);
TaskService service = new TaskService(repository);
Beispiel Spring-Test:
@MockBean
TaskService taskService;
Merksatz:
Nutze
mock()für Unit-Tests; nutze@MockBean, wenn Spring den Mock injizieren muss.
29. @SpyBean
@SpyBean wrappt einen echten Spring Bean mit einem Mockito-Spy.
Er kann echte Methoden aufrufen, erlaubt aber trotzdem Verifikation und Stubbing.
Vorsichtig verwenden.
Beispiel:
@SpyBean
private EmailService emailService;
Dann:
verify(emailService).sendWelcomeEmail(any());
Aber zu viel Spying kann Tests fragil machen.
Merksatz:
@SpyBeanbeobachtet einen echten Spring Bean — aber verwende ihn vorsichtig.
30. Testdaten
Tests brauchen Daten.
Optionen:
Daten im Testcode erzeugen
SQL-Skripte verwenden
TestEntityManager verwenden
Repositories verwenden
Fixtures/Builder verwenden
Migrationen mit sauberer Datenbank verwenden
Gute Praxis:
Mache Testdaten klar und nah am Test.
Beispiel:
TaskEntity task = new TaskEntity("Task A", "OPEN");
entityManager.persistAndFlush(task);
Merksatz:
Klare Testdaten machen Tests leichter verständlich.
31. Arrange, Act, Assert
Eine gängige Teststruktur:
Arrange
Act
Assert
Beispiel:
@Test
void completesTask() {
// Arrange
TaskEntity task = new TaskEntity("Task A", "OPEN");
when(taskRepository.findById(1L)).thenReturn(Optional.of(task));
// Act
TaskDto result = taskService.complete(1L);
// Assert
assertThat(result.status()).isEqualTo("DONE");
}
Merksatz:
Daten vorbereiten, einmal ausführen, Ergebnis prüfen.
32. Given, When, Then
Ein anderer Benennungsstil:
given
when
then
Beispiel:
@Test
void givenOpenTask_whenComplete_thenStatusIsDone() {
}
Oder einfacher:
@Test
void completesOpenTask() {
}
Gute Testnamen erklären das Verhalten.
Merksatz:
Testnamen sollten das Verhalten beschreiben.
33. Was macht einen guten Test aus?
Ein guter Test ist:
klar
schnell genug
wiederholbar
unabhängig
fokussiert
deterministisch
leicht zu debuggen
nah am Geschäftsverhalten
Schlechter Test:
hängt von der Testreihenfolge ab
hängt zufällig von der aktuellen Zeit ab
hängt von einer externen API ab
hat unklare Daten
testet viele Dinge gleichzeitig
erfordert manuelles Setup
Merksatz:
Gute Tests sind wiederholbar und leicht verständlich.
34. Unabhängige Tests
Jeder Test sollte unabhängig sein.
Schlecht:
Test B besteht nur, wenn Test A zuerst läuft.
Gut:
Jeder Test erzeugt seine eigenen Daten.
Jeder Test kann allein laufen.
Merksatz:
Tests sollten nicht von der Ausführungsreihenfolge abhängen.
35. Deterministische Tests
Ein deterministischer Test liefert jedes Mal dasselbe Ergebnis.
Schlechte Ursachen:
Zufallsdaten ohne Kontrolle
echte aktuelle Zeit
externe Netzwerkaufrufe
gemeinsamer veränderlicher Zustand
Abhängigkeit von der Testreihenfolge
echtes E-Mail-Versenden
echte Payment-API
Besser:
festen Clock verwenden
externe APIs mocken
isolierte Daten erzeugen
Testcontainers oder Test-Datenbank verwenden
Merksatz:
Ein Test sollte nicht mal bestehen und mal fehlschlagen.
36. Zeit testen
Schlechter Service:
public boolean isOverdue(TaskEntity task) {
return task.getDueDate().isBefore(LocalDate.now());
}
Schwerer zu testen, weil sich das aktuelle Datum ändert.
Besser:
public class TaskDeadlineService {
private final Clock clock;
public TaskDeadlineService(Clock clock) {
this.clock = clock;
}
public boolean isOverdue(TaskEntity task) {
LocalDate today = LocalDate.now(clock);
return task.getDueDate().isBefore(today);
}
}
Test:
Clock fixedClock = Clock.fixed(
Instant.parse("2026-07-07T10:00:00Z"),
ZoneOffset.UTC
);
Merksatz:
Injiziere
Clock, statt die aktuelle Zeit direkt in der Geschäftslogik aufzurufen.
37. Externe APIs testen
Rufe in normalen automatisierten Tests keine echten externen APIs auf.
Schlecht:
Test ruft echten Payment-Provider auf
Test sendet echte E-Mail
Test lädt in echten S3-Bucket hoch
Test ruft Produktions-API auf
Besser:
externen Client in Unit-/Slice-Tests mocken
Fake-Server in Integrationstests verwenden
Test-Umgebung/Sandbox vorsichtig nutzen
Merksatz:
Automatisierte Tests sollten nicht von echten externen Services abhängen.
38. Test-Datenbank-Optionen
Gängige Optionen:
H2/In-Memory-Datenbank
lokale PostgreSQL-Testdatenbank
Testcontainers PostgreSQL
Mock-Repository
Nutze je nach Testtyp.
| Option | Gut für |
|---|---|
| Mock-Repository | Service-Unit-Tests |
| H2 | schnelle, einfache Repository-Tests |
| Testcontainers | produktionsähnliche Datenbanktests |
| Lokale Test-DB | lokales Integrationstesting |
Merksatz:
Mocke Repositories für Service-Logik; echte Datenbank für Repository-Verhalten.
39. H2 vs. PostgreSQL
H2 ist schnell und einfach.
Aber es kann sich anders verhalten als PostgreSQL.
Unterschiede können sein:
SQL-Syntax
JSON-Spalten
Groß-/Kleinschreibung
Indizes
Constraints
Datum-/Zeitverhalten
Native Queries
Locking-Verhalten
Migrationsskripte
Für wichtige Persistenztests nutze denselben Datenbanktyp wie in der Produktion.
Merksatz:
H2 ist schnell, aber produktionsähnliche Datenbanktests finden produktionsähnliche Bugs.
40. Testcontainers-Vorschau
Testcontainers kann echte Services in Docker während Tests starten.
Beispiel-Services:
PostgreSQL
Redis
Kafka
RabbitMQ
LocalStack
Für Spring-Boot-Datenbanktests kann Testcontainers eine echte PostgreSQL-Datenbank bereitstellen.
Das schauen wir uns später an.
Merksatz:
Testcontainers liefert echte Infrastruktur für Integrationstests.
41. Welchen Testtyp soll ich wählen?
Frag dich:
Was will ich belegen?
Wenn du Geschäftslogik belegen willst:
Unit-Test
Wenn du Controller-Mapping und Validierung belegen willst:
@WebMvcTest
Wenn du eine Repository-Abfrage belegen willst:
@DataJpaTest
Wenn du den vollständigen Flow belegen willst:
@SpringBootTest
Wenn du echtes Datenbankverhalten belegen willst:
@DataJpaTest oder @SpringBootTest mit echter DB/Testcontainers
Merksatz:
Wähle den kleinsten Test, der das Verhalten belegt.
42. Beispiel: Task erstellen testen
Feature:
POST /api/tasks erstellt eine Aufgabe.
Mögliche Tests:
Unit-Test
Service-Geschäftslogik testen:
given valid request
when create task
then repository save is called
then DTO is returned
Web-Slice-Test
Controller testen:
given JSON request
when POST /api/tasks
then status 201
then validation errors return 400
Repository-Test
Repository testen:
given saved task
when findByStatus
then task is returned
Integrationstest
Vollständigen Flow testen:
given request
when POST /api/tasks
then controller, service, repository, database work together
Merksatz:
Ein Feature kann Tests auf verschiedenen Ebenen haben.
43. Was du nicht über-testen solltest
Vermeide Tests, die nur das Framework testen.
Schlecht:
@Test
void repositorySaveWorks() {
TaskEntity saved = taskRepository.save(new TaskEntity("A", "OPEN"));
assertThat(saved.getId()).isNotNull();
}
Das testet vor allem Spring Data/Hibernate.
Besser:
eigene Abfrage testen
Mapping-Constraint testen
Beziehung testen
Geschäftsverhalten testen
Validierung testen
Merksatz:
Teste deinen Code und deine Konfiguration — nicht Spring selbst.
44. Häufige Prüfungsfallen
Falle 1
Unit-Tests brauchen keinen Spring-Context.
Falle 2
@SpringBootTest lädt den vollständigen Application Context.
Falle 3
Vollständige Context-Tests sind langsamer als Unit-Tests.
Falle 4
Slice-Tests laden nur einen Teil der Anwendung.
Falle 5
@WebMvcTest testet die Web-Schicht, nicht den echten Service oder die Datenbank.
Falle 6
@DataJpaTest testet JPA-Repositories/Entities, nicht Controller.
Falle 7
@MockBean ersetzt einen Spring Bean im Test-Context.
Falle 8
Mockito mock() ist für einfache Unit-Tests gedacht.
Falle 9
Spring TestContext kann Contexts cachen.
Falle 10
@DirtiesContext kann Tests verlangsamen.
Falle 11
Nutze @ActiveProfiles("test") für Test-Konfiguration.
Falle 12
H2 kann sich anders verhalten als PostgreSQL.
Falle 13
Rufe in automatisierten Tests keine echten externen APIs auf.
Falle 14
Tests sollten unabhängig und deterministisch sein.
Falle 15
Wähle den kleinsten Test, der das Verhalten belegt.
45. Echte Prüfungsfrage: Unit-Test
Frage:
Was ist ein Unit-Test?
Antwort:
Ein Unit-Test prüft ein kleines Stück Code, üblicherweise eine Klasse oder Methode, ohne den Spring-Context oder echte Infrastruktur zu starten.
46. Echte Prüfungsfrage: Slice-Test
Frage:
Was ist ein Slice-Test?
Antwort:
Ein Slice-Test lädt nur einen bestimmten Teil des Spring Application Context — zum Beispiel die Web-Schicht oder die JPA-Schicht.
47. Echte Prüfungsfrage: Integrationstest
Frage:
Was ist ein Integrationstest?
Antwort:
Ein Integrationstest prüft, ob mehrere Teile der Anwendung zusammenarbeiten — oft mit einem Spring Application Context.
48. Echte Prüfungsfrage: @SpringBootTest
Frage:
Was macht @SpringBootTest?
Antwort:
@SpringBootTest lädt den vollständigen Spring-Boot-Application-Context für Integrationstests.
49. Echte Prüfungsfrage: @WebMvcTest
Frage:
Was testet @WebMvcTest?
Antwort:
@WebMvcTest testet die Web-/MVC-Schicht — zum Beispiel Controller, Request-Mapping, Validierung, JSON-Antworten und Controller Advice. Service-Abhängigkeiten werden oft gemockt.
50. Echte Prüfungsfrage: @DataJpaTest
Frage:
Was testet @DataJpaTest?
Antwort:
@DataJpaTest testet JPA-Komponenten wie Entities, Repositories, Mappings und Abfragen.
51. Echte Prüfungsfrage: @MockBean
Frage:
Was macht @MockBean?
Antwort:
@MockBean ersetzt einen Spring Bean im Application Context durch einen Mockito-Mock.
52. Echte Prüfungsfrage: TestContext
Frage:
Was macht das Spring TestContext-Framework?
Antwort:
Es verwaltet Spring Application Contexts für Tests, unterstützt Dependency Injection in Tests, Test-Properties, Profile, Transaktionen und Context-Caching.
53. Interview-Antwort
Frage:
Erkläre den Unterschied zwischen Unit-Tests, Slice-Tests und Integrationstests.
Gute Antwort:
Ein Unit-Test prüft ein kleines Stück Code ohne Spring zu starten — üblicherweise mit Mocks für Abhängigkeiten. Ein Slice-Test startet nur einen Teil des Spring-Contexts, zum Beispiel die MVC-Schicht mit @WebMvcTest oder die JPA-Schicht mit @DataJpaTest. Ein Integrationstest nutzt einen größeren oder vollständigen Spring-Context, oft mit @SpringBootTest, um zu prüfen, dass mehrere Teile der Anwendung zusammenarbeiten.
54. Interview-Antwort
Frage:
Wann würdest du
@SpringBootTestverwenden?
Gute Antwort:
Ich nutze @SpringBootTest, wenn ich den vollständigen Application Context brauche oder mehrere Schichten zusammen testen will — zum Beispiel Controller, Service, Repository, Security-Konfiguration und Datenbankintegration. Ich vermeide es für jeden Test, weil es langsamer ist als Unit- oder Slice-Tests.
55. Interview-Antwort
Frage:
Wann würdest du
@WebMvcTestverwenden?
Gute Antwort:
Ich nutze @WebMvcTest, um die Controller-Schicht zu testen. Es ist gut für Request-Mapping, Request-Body-Verarbeitung, Validierung, Response-Status, JSON-Antwort und Controller-Exception-Handling. Service-Abhängigkeiten mocke ich üblicherweise mit @MockBean.
56. Interview-Antwort
Frage:
Wann würdest du
@DataJpaTestverwenden?
Gute Antwort:
Ich nutze @DataJpaTest, um Repository-Abfragen, JPA-Mappings, Entity-Beziehungen, Constraints, Paginierung und Sortierung zu testen. Es fokussiert die Persistenzschicht und lädt keine Controller oder normalen Service-Beans.
57. Interview-Antwort
Frage:
Wie entscheidest du, welchen Testtyp du schreibst?
Gute Antwort:
Ich frage mich, welches Verhalten ich belegen will. Wenn es reine Geschäftslogik ist, schreibe ich einen Unit-Test. Wenn es Controller-Verhalten ist, nutze ich @WebMvcTest. Wenn es Repository-Verhalten ist, nutze ich @DataJpaTest. Wenn mehrere Schichten zusammenarbeiten müssen, nutze ich @SpringBootTest. Ich wähle den kleinsten Test, der das Verhalten belegt.
58. Kleine Code-Übung
Frage:
Wähle für jede Situation den besten Testtyp.
Situation 1
Ich will diese Methode testen:
public int calculateDiscount(int price, int percent) {
return price * percent / 100;
}
Bester Testtyp:
Unit-Test
Warum?
Kein Spring nötig.
Situation 2
Ich will testen, dass ungültiges JSON vom Controller 400 zurückgibt.
Bester Testtyp:
@WebMvcTest
Warum?
Das ist Web-/Controller-Verhalten.
Situation 3
Ich will testen, dass findByEmail funktioniert.
Bester Testtyp:
@DataJpaTest
Warum?
Das ist Repository-/Datenbankverhalten.
Situation 4
Ich will Login + Security + Controller + Datenbank zusammen testen.
Bester Testtyp:
@SpringBootTest
Warum?
Mehrere Anwendungsschichten müssen zusammenarbeiten.
59. Kleine Bug-Übung 1
Problem:
@SpringBootTest
class PriceCalculatorTest {
@Autowired
private PriceCalculator priceCalculator;
@Test
void calculatesDiscount() {
assertThat(priceCalculator.calculateDiscount(100, 10)).isEqualTo(10);
}
}
Frage:
Was ist das Problem?
Antwort:
Das ist reine Berechnungslogik. Den vollständigen Spring-Context zu starten ist unnötig. Nutze einen einfachen Unit-Test.
60. Kleine Bug-Übung 2
Problem:
@WebMvcTest(TaskController.class)
class TaskControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
void returnsTask() throws Exception {
mockMvc.perform(get("/api/tasks/1"))
.andExpect(status().isOk());
}
}
Aber der Controller hängt von TaskService ab, und der Test startet nicht.
Frage:
Was fehlt?
Antwort:
Ein Mock-Service-Bean fehlt.
@MockBean
private TaskService taskService;
61. Kleine Bug-Übung 3
Problem:
@DataJpaTest
class TaskServiceTest {
@Autowired
private TaskService taskService;
}
Frage:
Was ist falsch?
Antwort:
@DataJpaTest lädt den JPA-Slice, nicht normale Service-Beans. Nutze einen Unit-Test für Service-Logik oder @SpringBootTest, wenn Spring-Integration nötig ist.
62. Kleine Bug-Übung 4
Problem:
Ein Test besteht manchmal und schlägt manchmal fehl.
Mögliche Ursachen:
hängt von der aktuellen Zeit ab
hängt von der Testreihenfolge ab
hängt von Zufallsdaten ab
hängt von einer externen API ab
gemeinsamer veränderlicher Zustand
Lösung:
Daten explizit machen
festen Clock verwenden
externe Services mocken
Testreihenfolge-Abhängigkeit vermeiden
gemeinsamen Zustand zurücksetzen
Übungsfragen
Frage 1
Warum schreiben wir Tests?
Antwort:
Wir schreiben Tests, um das Anwendungsverhalten zu schützen, Bugs zu finden, Regressionen zu verhindern und zu dokumentieren, wie das System funktionieren soll.
Frage 2
Was ist die Testpyramide?
Antwort:
Die Testpyramide bedeutet: viele schnelle Unit-Tests, einige Slice-/Integrationstests und weniger langsame End-to-End-Tests.
Frage 3
Was ist ein Unit-Test?
Antwort:
Ein Unit-Test prüft ein kleines Stück Code — üblicherweise eine Klasse oder Methode — isoliert.
Frage 4
Braucht ein Unit-Test einen Spring-Context?
Antwort:
Nein. Ein echter Unit-Test startet üblicherweise keinen Spring-Context.
Frage 5
Was ist ein Slice-Test?
Antwort:
Ein Slice-Test lädt nur einen bestimmten Teil des Spring Application Context — zum Beispiel MVC oder JPA.
Frage 6
Was ist ein Integrationstest?
Antwort:
Ein Integrationstest prüft, ob mehrere Teile der Anwendung zusammenarbeiten.
Frage 7
Was macht @SpringBootTest?
Antwort:
@SpringBootTest lädt den vollständigen Spring-Boot-Application-Context.
Frage 8
Was testet @WebMvcTest?
Antwort:
@WebMvcTest testet die Web-Schicht — einschließlich Controller, Request-Mapping, Validierung, JSON und Controller Advice.
Frage 9
Was testet @DataJpaTest?
Antwort:
@DataJpaTest testet JPA-Komponenten wie Entities, Repositories, Mappings und Abfragen.
Frage 10
Was macht @MockBean?
Antwort:
@MockBean ersetzt einen Spring Bean im Test-Context durch einen Mockito-Mock.
Frage 11
Was ist der Unterschied zwischen mock() und @MockBean?
Antwort:
mock() erzeugt einen einfachen Mockito-Mock für Unit-Tests. @MockBean erzeugt/ersetzt einen Mock-Bean innerhalb des Spring-Test-Contexts.
Frage 12
Was ist Spring TestContext?
Antwort:
Spring TestContext verwaltet Spring Application Contexts für Tests und unterstützt Dependency Injection, Test-Properties, Profile, Transaktionen und Context-Caching.
Frage 13
Warum sind Spring-Tests langsamer als Unit-Tests?
Antwort:
Weil sie möglicherweise den Application Context starten, Beans erzeugen, Auto-Configuration laden, Datenbankverbindungen aufbauen, Filter initialisieren und Migrationen ausführen.
Frage 14
Was ist Context-Caching?
Antwort:
Context-Caching bedeutet, dass Spring denselben Test-Application-Context über Tests mit derselben Konfiguration wiederverwenden kann.
Frage 15
Was macht @DirtiesContext?
Antwort:
@DirtiesContext sagt Spring, dass der Context geändert wurde und nicht wiederverwendet werden soll.
Frage 16
Warum @ActiveProfiles("test") nutzen?
Antwort:
Um test-spezifische Konfiguration zu verwenden — zum Beispiel Test-Datenbank-URLs, Fake-Integrationen oder deaktivierte externe Services.
Frage 17
Warum kann H2 riskant für Repository-Tests sein?
Antwort:
Weil H2 sich anders verhalten kann als Produktionsdatenbanken wie PostgreSQL — besonders bei Native SQL, Constraints, JSON, Datum/Zeit und Locking-Verhalten.
Frage 18
Wofür ist Testcontainers nützlich?
Antwort:
Testcontainers ist nützlich, um echte Infrastruktur wie PostgreSQL, Redis oder Kafka in Docker während Integrationstests zu betreiben.
Frage 19
Was bedeutet deterministischer Test?
Antwort:
Ein deterministischer Test liefert jedes Mal dasselbe Ergebnis und schlägt nicht zufällig mal fehl und mal durch.
Frage 20
Wie wähle ich den richtigen Testtyp?
Antwort:
Wähle den kleinsten Test, der das Verhalten belegt: Unit-Test für Geschäftslogik, Slice-Test für eine Spring-Schicht, Integrationstest für mehrere Schichten.
Merksätze zum Mitnehmen
- Tests schützen das Verhalten.
- Unterschiedliche Probleme brauchen unterschiedliche Testtypen.
- Die Testpyramide hat viele Unit-Tests und weniger vollständige Integration-/E2E-Tests.
- Unit-Tests starten kein Spring.
- Slice-Tests laden nur einen Teil von Spring.
- Integrationstests prüfen mehrere Teile zusammen.
@SpringBootTestlädt den vollständigen Application Context.@WebMvcTesttestet die MVC-/Controller-Schicht.@DataJpaTesttestet Repositories und JPA-Mapping.@MockBeanersetzt einen Spring Bean durch einen Mock.- Nutze
mock()für Unit-Tests. - Nutze
@MockBeanin Spring-Tests. - Spring TestContext verwaltet Test-Application-Contexts.
- Spring-Tests sind langsamer, weil sie Spring-Infrastruktur starten.
- Spring cached Test-Contexts.
- Verwende
@DirtiesContextselten. - Nutze Test-Profile für Test-Konfiguration.
- H2 ist schnell, kann sich aber von PostgreSQL unterscheiden.
- Testcontainers liefert produktionsähnliche Infrastruktur.
- Tests sollten unabhängig und deterministisch sein.
- Wähle den kleinsten Test, der das Verhalten belegt.