comSysto bei XING

22
.
11
.
2016

Testen einer Spring-Boot-Web-App mit dem Spring-Test-Framework

Wir werden in diesem Blog-Post eine von mir mit Spring-Boot geschriebene Web-App mit dem Spring-Test-Framework testen. Verwenden werden wir dazu Spring-MVC-Tests und Junit und testen die View-Controller und die REST-Controller.

Dennis

Junior Software Engineer - Java, JVM

Testen einer Spring-Boot-Web-App mit dem Spring-Test-Framework

Wir werden in diesem Blog-Post eine von mir mit Spring-Boot geschriebene Web-App mit dem Spring-Test-Framework testen. Verwenden werden wir dazu Spring-MVC-Tests und Junit und testen die View-Controller und die REST-Controller.

Projekt-Specs

Die Web-App

Die App kann hier https://github.com/espendennis/mitfahr-zentrale gefunden werden. Es gibt zwei Modelle. Ein Offer-Model welches angebotene Fahrten repräsentiert und ein User-Model welches für Logins verwendet wird und ein paar persönliche Daten über den Benutzer hält. Für beide Modelle gibt es jeweils ein CrudRepository und eine Service-Schicht. Das User-Model implementiert UserDetails damit die User-Objekte zur Authentifizierung mit Spring-Security verwendet werden können. UserRepositoryUserDetailsService.java implementiert UserDetailService und wird ebenfalls für Spring-Security benötigt um die User-Objekte aus der Datenbank zu laden. Der HomeController ist der für die Page verantwortliche Controller. Hier sind alle Mappings enthalten, die nicht für die REST-Endpunkte sind. Die zurückgegebenen Pfade werden von Apache Tiles abgefangen. Entsprechend den Einstellungen in src/main/webapp/WEB-INF/tiles/layout/tiles.xml werden dann aus den verschiedenen tiles die html-Seiten aufgelöst. Die App hat zwei REST-Endpunkte. Für jedes Model einen. Die App verwendet einen MySQL-Server, für die Tests werden wir jedoch einen H2 verwenden.

Was soll gemacht werden?

Der HomeController und die beiden Controller für die REST-Endpunkte sollen mit 100% TestAbdeckung getestet werden. Hier im Blog wird nur auf das Offer-Model eingegangen werden. Die Tests für das User-Model unterscheiden sich kaum.

Wie werden wir testen?

Wir werden mit Hilfe von Mockmvc Anfragen bauen und an die Endpunkte senden.

Für die REST-Api werden wir die Antworten auf Format und Http-status-code prüfen. Falls Objekte zurückgegeben werden, werden wir diese parsen und mit Junit-Assertions prüfen.

Für den HomeController werden wir prüfen ob die richtigen views zurückgegeben werden, Weiterleitungen korrekt erfolgen und ob das Model die richtigen Daten enthält.

Das Setup

Alle Tests werden wir unter src/test/java/com/espen/tests/ ablegen. Im selben Pfad speichern wir ein ConfigFile für die Tests.

github:ad54389d4ababd6cffda2fb739bfe13b

Der Aufbau der Testklassen

github:ac1edf2271df75d33285d9e38453183c

Durch @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) wird beim Testen das komplette Web-Framework geladen und ein Embedded Tomcat-Server auf einen zufälligen Port gestartet. Dadurch laufen die Tests in einer gleichen Umgebung, wie die in der die WebApp laufen soll.

Mit @Transactional machen wir die Tests transactional. Dadurch werden alle Datenbankänderungen nach jedem Test wieder zurückgerollt.

Mit @TestPropertySource("test.properties") spezifizieren wir, dass für die Tests eine abweichende Konfiguration verwendet werden soll. Wenn der String, der an die Annotation übergeben wird mit einem / beginnt, wird dies als absoluter Pfad gewertet. Wenn nicht wird der Pfad als relativ zur Test-Klasse gewertet. Zu beachten ist hierbei, dass die Properties der Application zuerst geladen werden und daher in der test.properties nur noch die Einstellung stehen müssen, welche wir für die Tests überschreiben wollen.

Wir benötigen jeweils eine Instanz der Serviceschicht der beiden Modelle, einen WebApplicationContext für die MockMvc Klasse und einen ObjectMapper zum Parsen bzw. Serialisieren der Objekte. Diese werden Spring-üblich autowired.

Dann müssen noch ein paar Beispiel-Objekte erzeugt werden. Dies kann auch mit einem Skript von H2 ausgeführt werden. Wenn diese jedoch in der Testklasse instanziiert werden sind diese für Junit-Assertions ohne weiteres verfügbar. Beachtet werden muss, dass diese Objekte nur verwendet werden dürfen um die Datenbank vor den Tests zu füllen und um mit Assertions abzugleichen. Kein Test darf eines dieser Objekte verändern. Um dies abzusichern werden wir sie als final deklarieren.

Der erste Test

Die Tests sind immer nach demselben Prinzip aufgebaut. Testmethoden müssen public void und mit @Test annotiert sein.

github:30b415e2e2e987cf85ab7389e57e69af

Einen request bauen und senden

über mockMvc.perform() wird ein request ausgeführt. Dieser Methode muss ein RequestBuilder übergeben werden. Es gibt verschiedene RequestBuilder für verschiedene Request-Methoden. Wir werden get, post, put und delete requests benötigen. Die nötigen Imports sind:

github:ad7ab52eef1090d7ed6755d2f3aa3494

Dem request muss die Ziel-url übergeben werden. Er sieht dann so aus:

github:4fda07ec275bb4a5ff052ee7fb389fad

Überprüfen der Antwort

Nun müssen wir spezifizieren, was wir als Antwort erwarten. Dafür verwenden wir die Methode .andExpect().

github:ed018ff66c12490ba1c70a5f98b78428

Wir prüfen ob der zurückgegebene Status-Code stimmt und ob die Antwort JSON enthält. Zudem prüfen wir über .jsonPath() ob die Anzahl der JSON-Objekte in der Antwort stimmt. Über .jsonPath() kann auf alle Objekte bzw. Felder in der JSON-Antwort zugegriffen werden und so die Antworten überprüft werden. Es benötigt aber deutlich weniger Code die Antwort zu parsen und dann mit Junit die Objekte abzugleichen. Dafür benötigen wir erst die Anwort als String. Wenn wir .andReturn() anhängen gibt uns mockMvc die Antwort in Form eines MvcResult-Objektes zurück welches wir abfangen können.

github:08eef7a91a71e5b13748f64e6c6232c8

result.getResponse() gibt uns dann das MockHttpServletResponse-Objekt zurück. Mit dessen Methode .getContentAsString() erhalten wir das JSON-Objekt als String und können es parsen und mit Junit überprüfen ob die enthaltenen Objekte wirklich dieselben sind, die wir zuvor in die Datenbank gespeichert haben.

github:6b139323f34b2af9347b2eb522e772e1

Der komplette Test sieht jetzt so aus:

github:f355e3cb23244fa23912df6bbea74b1e

Zweiter Test

Natürlich müssen auch JSON-Daten gesendet werden. Dafür werden wir einen Test schreiben, in dem wir ein Angebot updaten.

Erst holen wir ein Angebot aus der Datenbank, ändern es und serialisieren es zu einem JSON-String:

github:10a6d5d0958344d70bf7b0b50b87ce3e

Dann erstellen wir wie im vorherigen Test eine Anfrage. Dieses mal verwenden wir den Builder für eine PUT-Anfrage. Ein Pfadparameter kann mit der gewohnten Syntax angegeben werden. Mit .contentType(MediaType.APPLICATION_JSON_UTF8) spezifizieren wir den Typ der Daten und mit .content(json) fügen wir den erstellten JSON-String hinzu. Die Antwort wird wie zuvor auf Typ und status-code geprüft und das result abgefangen.

github:64c369326de6dec1a37d21b020b5efa9

Dann müssen wir nur noch mit Junit-Assertions überprüfen ob die richtigen Änderungen in die Datenbank gespeichert wurden und keine ungewollten Änderungen.

github:c12e79bdb378393fa49d560606acea47

Nun müssen noch die restlichen Tests nach demselben Prinzip geschrieben werden und unsere finale Testklasse sieht dann so aus:

https://github.com/espendennis/mitfahr-zentrale/blob/master/src/test/java/com/espen/OfferApiTests.java

Den HomeController testen

Wir fangen wieder gleich an wie bei den Tests für die RESTApi:

github:7cfaa405b361166c6446c7f8e41bc7ae

Die Anfragen werden auch wieder nach demselben Prinzip aufgebaut. Nun prüfen wir aber mit .andExpect(view().name(“home”)) ob der richtige View zurückgegeben wird. Über .andExpect(forwardedUrl(“/WEB-INF/tiles/templates/baselayout.jsp”)) wird überprüft ob die Weiterleitung richtig funktioniert. Da in diesem Projekt Apache Tiles verwendet wird, werden alle Weiterleitungen auf das baselayout erfolgen und dann von Apache Tiles aufgelöst. Zuletzt wird noch überprüft ob das Model Daten enthält. Die Größe ist hier 1, da das Model eine Collection von 5 Offers enthält.

github:1b677b3ec42df6c46277d0a7790d8034

Wenn man Seiten testen will welche einen Login zur Weiterverarbeitung der Daten benötigen, kann

man durch anhängen von .with(user("username").password("password")) an den RequestBuilder Logins vollführen.

github:8c3c2ad447eb413399842227502df124

Hier ist zu sehen wie das Model auf Richtigkeit der Daten überprüft werden kann

github:edbba96d2ef9c0ace06004fa4f692421

Hier wird der UserLogin getestet:

github:f8122f70a55200a8ab7b124f673c4185

Formular Daten werden geprüft indem Parameter an den request angehängt werden. Zu beachten ist hier der Contenttype von MediaType.APPLICATION_FORM_URLENCODED.

github:b858e0bc517add273e76e3e5985ea41e

Und die fertige Testklasse:

https://github.com/espendennis/mitfahr-zentrale/blob/master/src/test/java/com/espen/HomeControllerTests.java

Wie der Screenshot zeigt ist die Test Coverage für die Controller nun 100%.

Screen Shot 2016-10-18 at 16.04.42.png
Leidenschaft, Freundschaft, Ehrlichkeit, Neugier. Du fühlst Dich angesprochen? Dann brauchen wir genau Dich.

Themen:

Kommentare?

comSysto Logo