## 1. Der Logik-Test (Unit Test)
Ziel: Eine einzelne Klasse (z.B. Service) isoliert testen. Rasend schnell. Keine Datenbank.
Annotation: @ExtendWith(MockitoExtension.class)
Wann nutzen? Wenn du komplexe if/else Logik, Berechnungen oder Abläufe in einem Service testen willst, ohne dass die Datenbank läuft.
Was passiert? Spring wird nicht gestartet. Alles, was die Klasse braucht (z.B. Repositories), wird „gemockt“ (gefaked).
Code Beispiel: Du hast einen RechnerService, der Daten aus der DB braucht, um etwas zu berechnen.
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.mockito.Mockito.when;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(MockitoExtension.class) // 1. Aktiviert Mockito
class RechnerServiceTest {
@Mock // 2. Erstelle einen Fake vom Repository (keine echte DB!)
DataRepository dataRepository;
@InjectMocks // 3. Stecke den Fake in den Service, den wir testen wollen
RechnerService rechnerService;
@Test
void testBerechnung() {
// A. Vorbereitung (Stubbing):
// "Wenn jemand findAll aufruft, gib eine Liste mit Wert 10 zurück"
when(dataRepository.findAllValues()).thenReturn(List.of(10, 20));
// B. Ausführung
int ergebnis = rechnerService.summiereAlles();
// C. Prüfung
assertEquals(30, ergebnis);
}
}
## 2. Der Datenbank-Test (Slice Test)
Ziel: Prüfen, ob deine SQL-Queries funktionieren.
Annotation: @DataJpaTest
Wann nutzen? Wenn du eigene SQL-Queries (@Query) geschrieben hast und wissen willst, ob sie syntaktisch korrekt sind und das Richtige finden.
Was passiert? Spring startet nur den Datenbank-Teil. Controller oder Services werden ignoriert. Es wird automatisch die H2 Datenbank genutzt.
Code Beispiel:
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@DataJpaTest // 1. Startet H2 und lädt Repositories
class UserRepositoryTest {
@Autowired // Hier kriegen wir das echte Repository (verbunden mit H2)
UserRepository userRepository;
@Test
void testSpeichernUndFinden() {
// A. Speichern
User user = new User("Max", "Mustermann");
userRepository.save(user);
// B. Suchen
User gefunden = userRepository.findByName("Max");
// C. Prüfen
assertThat(gefunden).isNotNull();
assertThat(gefunden.getNachname()).isEqualTo("Mustermann");
}
}
## 3. Der Integrations-Test (Ganze Backend-App)
Ziel: Prüfen, ob alle Teile (Controller -> Service -> DB) zusammenarbeiten.
Annotation: @SpringBootTest + @AutoConfigureMockMvc
Wann nutzen? Wenn du sicherstellen willst, dass deine API von außen erreichbar ist, das JSON korrekt ankommt, durch den Service läuft und in der (H2) DB landet.
Was passiert? Die komplette Spring App fährt hoch (im Test-Modus). Das dauert am längsten, testet aber am meisten.
Code Beispiel: Wir simulieren einen HTTP-Aufruf (wie er vom Frontend kommen würde).
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.MockMvc;
import org.junit.jupiter.api.Test;
// Importe für die fluent API von MockMvc
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
@SpringBootTest // 1. Ganze App hochfahren
@AutoConfigureMockMvc // 2. Tool aktivieren, um HTTP Requests zu faken
class UserControllerIntegrationTest {
@Autowired
MockMvc mockMvc; // Unser "Fake-Browser"
@Test
void testGetUsersEndpoint() throws Exception {
// Wir tun so, als würde das Frontend GET /api/users aufrufen
mockMvc.perform(get("/api/users"))
.andExpect(status().isOk()) // Erwarten HTTP 200 OK
.andExpect(content().json("[]")); // Erwarten leeres JSON Array (da DB leer)
}
}
Zusammenfassungen
# 🛡️ Spring Boot Testing Cheat Sheet
Ein Leitfaden für effektive Tests in Spring Boot Applikationen – von der kleinen Unit bis zur kompletten Integration.
---
## 🏗️ Die Test-Strategie im Überblick
Wir unterscheiden drei Ebenen des Testens. Je weiter unten in der Liste, desto schneller und isolierter laufen die Tests.
| Test-Art | Annotation | Ziel | Geschwindigkeit | DB (H2)? | Mockito? |
| :--- | :--- | :--- | :--- | :--- | :--- |
| **1. Unit Test** | `@ExtendWith(MockitoExtension.class)` | Reine Logik prüfen | ⚡️ Sehr schnell (< 100ms) | ❌ Nein | ✅ Ja |
| **2. Slice Test** | `@DataJpaTest` | Datenbank-Queries prüfen | 🐇 Mittel (ca. 1s) | ✅ Ja | ❌ Nein |
| **3. Integration** | `@SpringBootTest` | Gesamtes Backend prüfen | 🐢 Langsam (> 2s) | ✅ Ja | ❌ Nein |
---
## 1. Unit Tests (Logik & Service Layer)
**Wann?** Wenn du komplexe `if/else` Logik, Berechnungen oder Geschäftsabläufe testen willst.
**Warum?** Wir wollen nicht warten, bis eine Datenbank hochgefahren ist, nur um zu prüfen, ob `1 + 1 = 2` ist.
### Setup
* Spring wird **nicht** gestartet.
* Abhängigkeiten (z.B. Repositories) werden "gemockt" (simuliert).
### Code Beispiel
```java
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Optional;
import static org.mockito.Mockito.when;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(MockitoExtension.class) // 1. Mockito aktivieren
class CalculatorServiceTest {
@Mock // 2. Wir faken das Repository (Keine echte DB!)
UserRepository userRepository;
@InjectMocks // 3. Wir injizieren den Mock in den echten Service
CalculatorService calculatorService;
@Test
void testCalculateUserBonus() {
// A. Vorbereiten (Stubbing)
// "Wenn jemand User mit ID 1 sucht, gib einen Dummy zurück"
User dummyUser = new User(1, "Max", 100); // 100 Punkte
when(userRepository.findById(1)).thenReturn(Optional.of(dummyUser));
// B. Ausführen
int bonus = calculatorService.calculateBonus(1);
// C. Prüfen (Beispiel: Bonus ist 10% der Punkte)
assertEquals(10, bonus);
}
}
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@DataJpaTest // 1. Startet H2 und JPA Komponenten
class UserRepositoryTest {
@Autowired // 2. Das echte Repository (verbunden mit H2)
UserRepository userRepository;
@Test
void testSaveAndFind() {
// A. Speichern
User user = new User("Anna", "Musterfrau");
userRepository.save(user);
// B. Suchen
User found = userRepository.findByName("Anna");
// C. Prüfen
assertThat(found).isNotNull();
assertThat(found.getNachname()).isEqualTo("Musterfrau");
}
}
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.MockMvc;
import org.junit.jupiter.api.Test;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest // 1. Ganze App hochfahren
@AutoConfigureMockMvc // 2. HTTP-Requests simulieren
class UserControllerIntegrationTest {
@Autowired
MockMvc mockMvc; // Unser "Fake Browser"
@Test
void testEndpointIsReachable() throws Exception {
// Simuliert GET auf /api/users
mockMvc.perform(get("/api/users"))
.andExpect(status().isOk()); // Erwartet HTTP 200
}
}
Das ist eine klassische Schichten-Architektur (Layered Architecture). Hier ist der Schlachtplan, wie du jede Schicht testest, sortiert nach Wichtigkeit und Aufwand.
Wir gehen das von innen (Logik) nach außen (HTTP) durch.
1. Der Service (UseCase)
Status: Muss getestet werden! (Hier liegt deine Logik) Hier wird entschieden, ob ein User erstellt werden darf, Passwörter gehasht oder E-Mails versendet werden.
Wie: Klassischer Unit Test.
Werkzeuge: JUnit 5 & Mockito.
Spring Context: Nein! (Kein @SpringBootTest). Das macht den Test extrem schnell.
Strategie: Du testest den Service, aber du „simulierst“ (mockst) das Repository. Du willst nicht wirklich in die Datenbank schreiben.
@ExtendWith(MockitoExtension.class) // Aktiviert Mockito
class UserServiceTest {
@Mock
private UserRepository userRepository; // Das simulieren wir
@InjectMocks
private UserService userService; // Das testen wir
@Test
void sollteUserErstellen() {
// Given
User user = new User("Max");
// Wenn das Repo save aufgerufen wird, gib den User zurück
Mockito.when(userRepository.save(any(User.class))).thenReturn(user);
// When
User result = userService.createUser("Max");
// Then
Assertions.assertEquals("Max", result.getName());
// Prüfen, ob das Repo wirklich genau 1x aufgerufen wurde
Mockito.verify(userRepository, Mockito.times(1)).save(any());
}
}
2. Der Controller
Status: Sollte getestet werden (Schnittstellen-Test) Hier prüfst du nicht die Logik (das hast du ja schon im Service gemacht), sondern nur, ob der Controller HTTP richtig spricht (Statuscodes, JSON-Format).
Wie: Slice Test (Integrationstest „Light“).
Werkzeuge: @WebMvcTest, MockMvc.
Spring Context: Ja, aber nur der Controller-Teil.
Strategie: Du fährst den Controller hoch, aber mockst den Service darunter weg.
Code-Beispiel:
@WebMvcTest(UserController.class) // Startet nur diesen Controller
class UserControllerTest {
@Autowired
private MockMvc mockMvc; // Damit simulieren wir HTTP-Anfragen
@MockBean
private UserService userService; // Den Service mocken wir weg
@Test
void postSollte201Zurueckgeben() throws Exception {
// Wir tun so, als ob der Service erfolgreich war
Mockito.when(userService.createUser(anyString())).thenReturn(new User("Max"));
mockMvc.perform(post("/users")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"name\": \"Max\"}"))
.andExpect(status().isCreated()) // Erwarten HTTP 201
.andExpect(jsonPath("$.name").value("Max")); // Prüfen JSON
}
}
Das ist eine klassische Schichten-Architektur (Layered Architecture). Hier ist der Schlachtplan, wie du jede Schicht testest, sortiert nach Wichtigkeit und Aufwand.
Wir gehen das von innen (Logik) nach außen (HTTP) durch.
1. Der Service (UseCase)
Status: Muss getestet werden! (Hier liegt deine Logik) Hier wird entschieden, ob ein User erstellt werden darf, Passwörter gehasht oder E-Mails versendet werden.
Wie: Klassischer Unit Test.
Werkzeuge: JUnit 5 & Mockito.
Spring Context: Nein! (Kein @SpringBootTest). Das macht den Test extrem schnell.
Strategie: Du testest den Service, aber du „simulierst“ (mockst) das Repository. Du willst nicht wirklich in die Datenbank schreiben.
Code-Beispiel:
Java
@ExtendWith(MockitoExtension.class) // Aktiviert Mockito
class UserServiceTest {
@Mock
private UserRepository userRepository; // Das simulieren wir
@InjectMocks
private UserService userService; // Das testen wir
@Test
void sollteUserErstellen() {
// Given
User user = new User(„Max“);
// Wenn das Repo save aufgerufen wird, gib den User zurück
Mockito.when(userRepository.save(any(User.class))).thenReturn(user);
// When
User result = userService.createUser(„Max“);
// Then
Assertions.assertEquals(„Max“, result.getName());
// Prüfen, ob das Repo wirklich genau 1x aufgerufen wurde
Mockito.verify(userRepository, Mockito.times(1)).save(any());
}
}
2. Der Controller
Status: Sollte getestet werden (Schnittstellen-Test) Hier prüfst du nicht die Logik (das hast du ja schon im Service gemacht), sondern nur, ob der Controller HTTP richtig spricht (Statuscodes, JSON-Format).
Wie: Slice Test (Integrationstest „Light“).
Werkzeuge: @WebMvcTest, MockMvc.
Spring Context: Ja, aber nur der Controller-Teil.
Strategie: Du fährst den Controller hoch, aber mockst den Service darunter weg.
Code-Beispiel:
Java
@WebMvcTest(UserController.class) // Startet nur diesen Controller
class UserControllerTest {
@Autowired
private MockMvc mockMvc; // Damit simulieren wir HTTP-Anfragen
@MockBean
private UserService userService; // Den Service mocken wir weg
@Test
void postSollte201Zurueckgeben() throws Exception {
// Wir tun so, als ob der Service erfolgreich war
Mockito.when(userService.createUser(anyString())).thenReturn(new User(„Max“));
mockMvc.perform(post(„/users“)
.contentType(MediaType.APPLICATION_JSON)
.content(„{\“name\“: \“Max\“}“))
.andExpect(status().isCreated()) // Erwarten HTTP 201
.andExpect(jsonPath(„$.name“).value(„Max“)); // Prüfen JSON
}
}
3. Das Repository
Status: Nur bei eigenen Queries testen Spring Data JPA (findBy…, save, delete) ist von den Spring-Entwicklern bereits getestet. Du musst nicht testen, ob save() funktioniert – das tut es.
Wann testen: Wenn du eine eigene SQL/JPQL Query schreibst: @Query(„SELECT u FROM User u WHERE …“).
Wie: @DataJpaTest. Das fährt eine echte kleine Datenbank (oft H2 In-Memory) hoch.
—
4. Die Entity
Status: Meistens kein Test nötig Eine Entity ist oft nur ein Datencontainer (POJO) mit Gettern und Settern.
Wann testen: Nur wenn du komplexe Methoden in der Entity hast (z. B. user.isBirthdayToday()). Getter und Setter testet man nicht.
| Komponente | Was wird getestet? | Annotation | Mocking? |
| Service | Die Logik (Berechnungen, Abläufe) | @ExtendWith(MockitoExtension.class) |
Ja (Repo) |
| Controller | HTTP & JSON (Routing, Statuscodes) | @WebMvcTest(UserController.class) |
Ja (Service) |
| Repository | Eigene SQL-Queries | @DataJpaTest |
Nein (echte DB) |
| Entity | Nichts (außer komplexe Methoden) | – | – |
1. Die Test-Klasse benennen
Die Regel ist simpel: NameDerKlasse + Test.
Original-Klasse: UserUseCase
Test-Klasse: UserUseCaseTest