Zum Hauptinhalt springen

Woche 7, Tag 1 — Spring Boot Testing Mental Model

Ziel

Heute verstehst du das große Bild beim Testen in Spring Boot.

Die Kernfragen:

  1. Warum schreiben wir Tests?
  2. Was ist die Testpyramide?
  3. Was ist ein Unit-Test?
  4. Was ist ein Slice-Test?
  5. Was ist ein Integrationstest?
  6. Was ist der Spring TestContext?
  7. Warum sind Spring-Tests langsamer als reine Unit-Tests?
  8. Was macht @SpringBootTest?
  9. Was sind Test-Slices wie @WebMvcTest und @DataJpaTest?
  10. Wann solltest du Mocks verwenden?
  11. Wann solltest du den echten Spring-Context verwenden?
  12. 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.
  • SecurityFilterChain definiert Sicherheitsregeln.
  • UserDetailsService lädt Benutzer.
  • PasswordEncoder hasht Passwörter.
  • @PreAuthorize schü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

TesttypWas getestet wirdSpring-Context?Geschwindigkeit
Unit-Testeine Klasse/Methodeneinsehr schnell
Slice-Testeine Spring-Schichtteilweisemittel
Integrationstestmehrere Schichten/ganze Appjalangsamer
End-to-End-Testechter Nutzerflussoft ganzes Systemam 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

AnnotationTestet
@WebMvcTestController, MVC, Validierung, JSON, MockMvc
@DataJpaTestJPA-Entities, Repositories, Abfragen
@JsonTestJSON-Serialisierung/Deserialisierung
@RestClientTestREST-Clients
@JdbcTestJDBC-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:

@WebMvcTest testet 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:

@DataJpaTest testet 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:

@SpringBootTest startet 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 + MockMvc testet 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:

ModusBedeutung
MOCKMock-Servlet-Umgebung, kein echter Server
RANDOM_PORTstartet echten Server auf zufälligem Port
DEFINED_PORTstartet echten Server auf konfiguriertem Port
NONEkeine Web-Umgebung

Merksatz:

MOCK ist üblich mit MockMvc; RANDOM_PORT startet 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_PORT testet ü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 @DirtiesContext selten.


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:

@MockBean mockt einen Spring Bean innerhalb des Test-Contexts.


28. Mockito mock() vs. @MockBean

WerkzeugWo es funktioniert
mock()einfacher Unit-Test
@MockMockito-Test
@MockBeanSpring-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:

@SpyBean beobachtet 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.

OptionGut für
Mock-RepositoryService-Unit-Tests
H2schnelle, einfache Repository-Tests
Testcontainersproduktionsähnliche Datenbanktests
Lokale Test-DBlokales 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 @SpringBootTest verwenden?

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 @WebMvcTest verwenden?

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 @DataJpaTest verwenden?

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.
  • @SpringBootTest lädt den vollständigen Application Context.
  • @WebMvcTest testet die MVC-/Controller-Schicht.
  • @DataJpaTest testet Repositories und JPA-Mapping.
  • @MockBean ersetzt einen Spring Bean durch einen Mock.
  • Nutze mock() für Unit-Tests.
  • Nutze @MockBean in Spring-Tests.
  • Spring TestContext verwaltet Test-Application-Contexts.
  • Spring-Tests sind langsamer, weil sie Spring-Infrastruktur starten.
  • Spring cached Test-Contexts.
  • Verwende @DirtiesContext selten.
  • 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.