Skip to content

Tag 12: VORMITTAG & Integrationtest

MaximBenkert edited this page Mar 16, 2023 · 1 revision

Lombok (library)


Lombok ist eine Java Bibliothek, die die Möglichkeiten der automatischen Code-Generierung erweitert. Hierfür werden Annotationen genutzt.

Häufigste Annotationen

  • @Getter & @Setter
  • @ToString
  • @EqualsAndHashCode
  • @NoArgsConstructor & @AllArgsConstructor
  • @Data (ersetzt alle oben genannten Annotations)
  • @Value (ersetzt alle oben genannten Annotations, außer @Setter)
  • @Builder (erstellt einen Builder für die Klasse)

Beispiel ohne Lombok (= 54 Zeilen)


    private String id;
    private String name;

    public Product() {
    }

    public Product(String id, String name) {
        this.id = id;
        this.name = name;
    }


    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Product product)) return false;

        if (getId() != null ? !getId().equals(product.getId()) : product.getId() != null) return false;
        return getName() != null ? getName().equals(product.getName()) : product.getName() == null;
    }

    @Override
    public int hashCode() {
        int result = getId() != null ? getId().hashCode() : 0;
        result = 31 * result + (getName() != null ? getName().hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return "Product{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}

Beispiel mit Lombok (= 12 Zeilen)

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {

    private String id;
    private String name;
}

Pro & Contra Lombok

Vorteile Nachteile
kürzerer Code externe Library
weniger Fehlerquellen Updates der Library als potentielles Risiko
weniger zu testender Code Code wenig nachvollziehbar für Entwickler, die nicht mit Lombok vertraut sind

Lombok-Maven Depency zum Einfügen in pom.xml Datei:


Records

Ein Record ist eine spezielle Klasse, die als "schlanke read-only Datenstruktur" verwendet wird. Auch so kann wieder Code gespart werden. Wird eigentlich nur auf Model- (=Daten-) Klassen angewendet. Attribute sind immer private und final.

Im Record werden automatisch generiert:

  • Getter (es gibt keinen Setter, da immutable!)
  • equals() & `hasCode()
  • toString()
  • --> ein Leer-Konstruktor wird nicht generiert

Beispiel Sytax: Eigenschaften werden in runden Klammern deklariert

public record Product(
        String id,
        String name
) {
}

Pro & Contra Records

Vorteile Nachteile
immutable, also unveränderbar immutable, also unveränderbar
kürzerer Code kann nicht von anderen Klassen erweitert werden (Inheritance)
weniger zu testender Code Code wenig nachvollziehbar für Entwickler, die nicht mit Records vertraut sind
weniger Fehlerquellen
können mit Lombok kombiniert werden

Integrationtest


Integrationstest testet, ob im Projekt alle Abhängigkeiten zusammen als Ganzes funktionieren (Test, der alle drei Schichten - s.u. - durchläuft). Getestet wird im Dreischichtenmodell von oben nach unten:

Screenshot 2023-03-15 at 14 22 32

Test mit allen zugehörigen Detailkommentaren

mockMvC: Objekt, über welches Anfragen an Server versendet werden können (wird neu erstellt via @Autowired)

@DirtiesContext: entweder über die Klasse schreiben, um nach jedem Test neutralen zustand herzustellen, oder: über alle Tests schreiben, welche neutralen Zustand benötigen


// Wir schreiben einen Integrationstest und stellen sicher dass:
// - die HTTP Methoden ausgeführt werden
// - der Service läuft
// - das Repo die Daten hergibt

// @SpringBootTest = Fährt den Spring Kontext hoch (Server, Datenbankverbindung, erzeugt Objekte, etc.)
// @AutoConfigureMockMvc = Konfiguriert MockMvc nach unseren Testbedürfnissen
@SpringBootTest
@AutoConfigureMockMvc
class ShopIntegrationTest {

     // Mock = So was wie Nachmachen 
     // MockMvc ermöglicht uns Anfragen nachzumachen 
     // Damit mockMvc NICHT null ist, müssen wir die Abhängigkeit, d.h. die DEPENDENCY bereitstellen... 
    @Autowired
    MockMvc mockMvc;

    @Autowired
    ProductRepository productRepository;

    // 1. Wurde die Anfrage verstanden und angenommen? -> Status 200 = OK
    // 2. Bekommen wir alle Produkte zurück?
    // Wenn die Datenbank leer ist -> bekommen wir eine leere Liste []

    // Wenn wir alle Produkte abrufen
    // Sollen alle Produkte zurück kommen
    // Testmethode + sollte ...
    // PS: Es könnte vllllt eine Exception entstehen, das ist uns dann aber egal, weil dann ist der Test eben fehlgeschlagen
    @Test
    void getAllProducts_shouldReturnEmptyList_whenRepositoryIsEmpty() throws Exception {
        // mockMvc.perform -> wie bei Postman, verschickt eine Anfrage
        // GET = HTTP Verb um etwas anzufragen
        mockMvc.perform(get("/api/shop/products"))
                .andExpect(status().isOk())
                .andExpect(content().json(
                        """
                                []
                                """));
    }

    @Test
    // @DirtiesContext = Putzkraft => Wenn du etwas eingefügt hast ins Repo, lösche es danach wieder!
    // Immer über den Methoden, die den Speicher verändern
    @DirtiesContext
    void getAllProducts_shouldReturnListWithOneProduct_whenRepositoryHasOneProduct() throws Exception {
        // Wie kriegen wir etwas ins Repo?
        // zB per POST .. oder?
        // einfach direkt mit einem Repositoryaufruf!
        Product product = new Product("Georgischer Rotwein", "1");
        productRepository.add(product);

        // mockMvc.perform -> wie bei Postman, verschickt eine Anfrage
        // GET = HTTP Verb um etwas anzufragen
        mockMvc.perform(get("/api/shop/products"))
                .andExpect(status().isOk())
                .andExpect(content().json(
                        """
                                [
                                   {
                                    "name": "Georgischer Rotwein",
                                    "id": "1"
                                  }
                                ]
                                """));
    }

    // Wenn wir ein Produkt an den Controller senden, wird es erstellt.
    // Dieses neu erstellte Produkt erwarten wir als Antwort
    @Test
    @DirtiesContext
    void addProduct_shouldReturnCreatedProduct() throws Exception {
        // Wir verschicken eine POST Anfrage an die URL
        mockMvc.perform(
                        // WAS WIR VERSCHICKEN
                        post("/api/shop/products")
                                // Das ist unser Format - wir verschicken fast immer JSON
                                .contentType(MediaType.APPLICATION_JSON)
                                // Was im Body beim POST-Request verschickt wird
                                .content(
                                        """
                                                 {
                                                     "name": "Primitivo Wein",
                                                     "id": "1"
                                                 }
                                                """
                                ))
                // VERGLEICH - IST UNSER ERGEBNIS RICHTIG?
                .andExpect(
                        // Der Status den wir zurück bekommen
                        status().isOk()
                )
                .andExpect(
                        // Das ist der Request Body - Der Inhalt den wir bekommen
                        content().json(
                                """
                                         {
                                             "name": "Primitivo Wein",
                                             "id": "1"
                                         }
                                        """
                        ));
    }
}

Imports

Beim Coach gab es Importprobleme, einen Import hatte er manuell hinzugefügt (ich meine es war: MockMvcRequestBuilders.get), hierfür nochmal alle Imports untenstehend falls das hier auch bei jemandem vorkommt:

import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.junit.jupiter.api.Assertions.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;)

Clone this wiki locally