Kleiner Bruder API

https://bildung.social/@oerinformatik/

https://oer-informatik.de/sbb02_kleiner-bruder-api

tl/dr; _(ca. 6 min Lesezeit): Wir implementieren eine API, die wie ein kleiner Bruder antwortet: Allem gesagten wird “Selber” vorangestellt. An diesem dämlichen Beispiel lernen wir einen Controller für GET-Requests und das Unit-Test-Framework jUnit kennen. Dieser Artikel ist ein Teil der Artikelserie zu einem Adressbuch-SpringBoot-Projekt. Weiter geht es dann wieder mit dem eigentlichen Projekt. Zuerst ein Modell für die Datenhaltung: das Modell zur Verarbeitung von Adressen.

Implementieren der API

Das “Hello World” ist ja schon ein Anfang. Bevor wir beginnen, die eigentliche API zu erstellen, möchte ich aber mit einer kleinen Spielerei weiter Springboot kennenlernen.

Jeder kennt das Kindergartenspiel, bei dem das eine Kind etwas (oft beleidigendes) sagt und das andere Kind diese Zuweisung mit vorangestelltem “Selber” wiederholt:

  • Du Schaf!
  • Selber Schaf!

Derlei Spiele werden mit Begeisterung von kleinen Brüdern aufgenommen, wir werden die API, die genau das umsetzen soll, also “kleiner Bruder API” nennen.

Die Klasse “AddressbookApplication.java” ist noch vom “Hello World”-Beispiel als @RestController annotiert, sie reagiert also auf HTTP-Requests und sucht für eingegebene Pfade (Routen) zugehörige Methoden. Wir fügen für die Route http://localhost:8085/kleinerBruder eine neue Methode ein:

Hier ist allerdings die Besonderheit, dass die Route einen Parameter enthält ({zuweisung}), den wir auch als Parameter an die Methode übergeben (durch die Annotation @PathVariable gekennzeichnet). Wir können also die Zeichenkette, die nach dem letzten “/” in der URL übergeben wird in der String-Variablen zuweisung nutzen:

Die Kleine-Bruder-API antwortet immer mit “Selber …”
Die Kleine-Bruder-API antwortet immer mit “Selber …”

Get-Requests näher untersuchen

Wir können mit dem HTTP-GET-Request auf der Ressource “localhost:8085/kleinerBruder/…” (nichts anderes ist der Aufruf im Browser) also eine parametrisierte Antwort erhalten und direkt im Browser als Text zurückgeben lassen.

In einem Linux/MacOSX-Terminal mit installiertem curl lässt sich dieser Get-Request ebenso abschicken - und zudem das ganze etwas besser testen, da wir bei Bedarf mehr Informationen zurück geliefert bekommen:

Um unter Windows über die PowerShell Get-Requests abzusetzen dient das Commandlet Invoke-WebRequest (curl wird auch erkannt, ist hier ein Alias für CmdLets Invoke-WebRequest und muss entsprechend parametrisiert werden):

    StatusCode        : 200
    StatusDescription :
    Content           : Selber Hornochse
    RawContent        : HTTP/1.1 200
                        Keep-Alive: timeout=60
                        Connection: keep-alive
                        Content-Length: 16
                        Content-Type: text/plain;charset=UTF-8
                        Date: Mon, 05 Sep 2022 10:58:02 GMT

                        Selber Hornochse
    Forms             : {}
    Headers           : {[Keep-Alive, timeout=60], [Connection, keep-alive], [Content-Length, 16], [Content-Type,
                        text/plain;charset=UTF-8]...}
    Images            : {}
    InputFields       : {}
    Links             : {}
    ParsedHtml        : mshtml.HTMLDocumentClass
    RawContentLength  : 16

Testen der API

Test vorbereiten

Anhand dieser kleinen API soll direkt eine Testumgebung erstellt werden. Das Framework hat hierzu bereits unter src/test/... eine jUnit-Testklasse vorbereitet, die wir nur noch mit Leben füllen müssen. Wir können über die Datei- oder über die Projektansicht zu den Testklassen navigieren.

Unter src/test befindet sich die Vorlage für jUnit
Unter src/test befindet sich die Vorlage für jUnit

Als Voraussetzung für den Test benötigen wir ein Umfeld, in dem unsere HTTP-Requests aufgerufen werden (der Test soll ja unabhängig von Browsern oder curl-Aufrufen automatisch laufen). Hierzu benötigen wir ein Objekt der Klasse WebApplicationContext, das unsere App repräsentiert. Über die Annotation @Autowired weiß das SpringBoot-Framework, dass es selbst die zugehörigen Instanzen dieser Klassen erzeugen und verknüpfen muss. Ferner brauchen wir ein Objekt der Klasse MockMvc, über das wir die HTTP-Aufrufe simulieren. Dessen Instanziierung folgt im nächsten Abschnitt.

Die Tests können erst durchgeführt werden, wenn das Mockobjekt instanziiert ist. Bei jedem Test soll eine eigene Instanz von MockMvc erzeugt werden. Mit Hilfe der jUnit5-Annotation @BeforeEach wird die Methode setup() vor jedem Test ausgeführt und erzeugt eine neue Instanz von MockMvc:

(Hinweis: falls Sie auf alte Beispiele stoßen, die mit @Before annotiert sind, so handelt es sich um jUnit4-Tests. Diese sind nicht kompatibel mit dem neueren jUnit5!)

Aufbau der Testfälle

Es ist üblich Testfälle in einzelne Schritte aufzuteilen, etwa in die Vorbereitung der Testdaten, das Ausführen der Methodenaufrufe und das Vergleichen der Ergebnisse mit den Erwartungen. Martin Fowler definiert die Phasen given, when, then folgendermaßen: 1:

  • The given part describes the state of the world before you begin the behavior you’re specifying in this scenario. You can think of it as the pre-conditions to the test.

  • The when section is that behavior that you’re specifying.

  • Finally the then section describes the changes you expect due to the specified behavior.

Auf das Beispiel zugeschnitten heißt das:

  • In der given -Sektion werden alle Vorbedingungen gesetzt. Erforderliche Instanzen erzeugt, Variablen gesetzt, Dienste gestartet. In unserem Fall wird hier nur festgelegt, dass der mit dem Pfad (der URI http://localhost:8085/kleinerBruder/...) übergebene Wert “Hornochse” ist:
  • In der when-Sektion wird der gemockte HTTP-Aufruf (ein GET-Request) vorbereitet und in der Variable testRequest gespeichert.
  • In der then-Sektion wird der erwartete Rückgabewert definiert (expected). Dann wird in der Mocking-Umgebung (mockMVC) der vorbereitete Request abgesetzt (.perform()) und der Rückgabewert mit dem erwarteten Wert verglichen (.andExpect()).

Nach dieser Logik erstellen wir zwei Tests: einen, der überprüft, ob der HTTP-StatusCode 200 (Ok) zurückgegeben wird und einen zweiten, der den Inhalt der Antwort überprüft:

Was fehlt sind noch die nötigen Imports. In der Regel lassen diese sich über die Tooltips automatisch finden. Im Ganzen sollte es am Ende etwa so aussehen:

package de.csbme.ifaxx.addressbook;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;


@SpringBootTest
class AddressbookApplicationTests {

    @Autowired
    private WebApplicationContext webApplicationContext;

    private MockMvc mockMvc;

    @BeforeEach
    public void setUp() {
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }

    @Test
    public void testSelberAntwort_httpStatusIsOK() throws Exception {
        // given
        String pathparam = "Hornochse";

        // when
        MockHttpServletRequestBuilder testRequest = MockMvcRequestBuilders.get("/kleinerBruder/"+pathparam);

        // then
        ResultMatcher expected = MockMvcResultMatchers.status().isOk();
        mockMvc.perform(testRequest).andExpect(expected);
    }

    @Test
    public void testSelberAntwort_contentLoads() throws Exception {
        // given
        String pathparam = "Hornochse";

        // when
        MockHttpServletRequestBuilder testRequest = MockMvcRequestBuilders.get("/kleinerBruder/"+pathparam);

        // then
        ResultMatcher expected = MockMvcResultMatchers.content().string("Selber "+pathparam);
        mockMvc.perform(testRequest).andExpect(expected);
    }

}

Jetzt können die Tests ausgeführt werden: Im einfachsten Fall, in dem die Quelltextdatei der Tests geöffnet und ausgeführt wird (Ausführen - Debugging starten / F5 / Play-Symbol). Das Ergebniss wird etwa so dargestellt:

Ergebnis der Tests
Ergebnis der Tests

Um sicher zu gehen, dass die Tests funktionieren, sollten ruhig versuchsweise Änderungen an dem Code vorgenommen werden, die zu scheiternden Tests führen müssten.

Nächster Schritt

Dieser Artikel ist ein Teil der Artikelserie zu einem Adressbuch-SpringBoot-Projekt.

Weiter geht es dann wieder mit dem eigentlichen Projekt. Zuerst ein Modell für die Datenhaltung: das Modell zur Verarbeitung von Adressen.


Hinweis zur Nachnutzung als Open Educational Resource (OER)

Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY-SA 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: Kleiner Bruder API” von Hannes Stein, Lizenz: CC BY-SA 4.0. Der Artikel wurde unter https://oer-informatik.de/sbb02_kleiner-bruder-api veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/java-springboot/Backend. Stand: 04.09.2023.

[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]


  1. Given When Then

Kommentare gerne per Mastodon, Verbesserungsvorschläge per gitlab issue (siehe oben). Beitrag teilen: