Skip to main content

## 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

 

Leave a Reply