Start / Seminare / Spring Boot 4 Grundlagen

Modul

Spring-Boot-Anwendungen testen

5 Kapitel in diesem Modul-Video · Laufzeit

Vertonung mit synthetischer Stimme · Inhalte redaktionell verantwortet · © 2026 HECKER CONSULTING, alle Rechte vorbehalten

Transkript

Der gesprochene Text dieses Moduls zum Mitlesen, Überfliegen und Durchsuchen. Ein Klick auf einen Zeitstempel springt an die Stelle im Video.

Spring-Boot-Anwendungen testen

0:00 Über Tests wird viel geredet und dabei meist über die falsche Frage. Die interessante Frage lautet nämlich nicht, ob man testet, sondern wie viel Anwendung ein Test hochfährt, um seine Aussage zu treffen. Genau darum geht es in diesem Modul. Spring Boot bietet mehrere Zuschnitte an: einen, der nur die Webschicht lädt, einen für die Datenzugriffsschicht, einen für das volle Zusammenspiel.

0:22 Jeder hat seinen Platz, und die Kunst besteht darin, den passenden zu wählen. Denn ein Test, der die halbe Anwendung startet, um eine Methode zu prüfen, misst vor allem Ihre Geduld.

Spring-Boot-Anwendungen testen

0:34 Wir arbeiten uns von innen nach außen vor. Zuerst das Grundmuster und die beiden Testarten. Dann prüfen wir die Webschicht — ohne Tomcat, ohne Datenbank. Danach die Datenzugriffsschicht, diesmal mit echter Datenbank, aber ohne Web. Anschließend der Integrationstest, der alles zusammen und über einen echten Port prüft. Und zum Schluss Testcontainers, mit dem sich externe Systeme für die Dauer eines Tests hochfahren lassen. Fünf Zuschnitte, und jeder hat eine klare Aufgabe.

Testen mit Spring Boot

1:03 Beginnen wir mit dem Muster, das allen Spring-Tests zugrunde liegt. Es ist immer dasselbe, egal welchen Zuschnitt Sie wählen — und wenn Sie es einmal verstanden haben, erklären sich die Unterschiede fast von selbst. Das Muster hat drei Schritte: Kontext aufbauen, Abhängigkeiten ersetzen, Verhalten prüfen. Sie bauen einen Anwendungskontext, der die zu prüfende Komponente enthält, setzen an die Stelle ihrer Abhängigkeiten Attrappen, deren Verhalten Sie steuern, und prüfen dann, ob die Komponente das Erwartete tut.

1:33 Der letzte Satz auf der Folie ist der interessanteste: Dass sich Abhängigkeiten so leicht ersetzen lassen, ist ein unmittelbarer Gewinn der Injektion. Wer Abhängigkeiten in seinen Klassen selbst erzeugt, kann sie im Test nicht austauschen. Testbarkeit ist also keine eigene Eigenschaft, sondern eine Folge sauberer Konstruktion.

1:53 Lesen Sie diese Tabelle als zwei Werkzeuge mit unterschiedlichem Zweck, nicht als besser und schlechter. Links der Modultest: eine Komponente allein, alle Abhängigkeiten als Attrappen, keine Datenbank, sehr kurze Laufzeit — und deshalb viele davon. Rechts der Integrationstest: das Zusammenspiel, echte oder testspezifische Abhängigkeiten, spürbar länger — und deshalb wenige, gezielt gesetzt.

2:16 Die letzte Zeile ist die eigentliche Empfehlung. Wenn Ihre Testsuite zu langsam wird, liegt es fast immer daran, dass sie zu viele Integrationstests enthält, die als Modultests reichen würden. Der erste Punkt ist der wertvollste: vorgefertigte Zuschnitte je Schicht. Sie müssen also nicht selbst entscheiden, welche Beans ein Webschichttest braucht — das ist bereits durchdacht. Der zweite bringt die Werkzeuge für Attrappen und Prüfungen mit.

2:42 Der dritte ist eine angenehme Nebenwirkung der Starter: JUnit und Mockito sind ohne Zutun im Projekt. Und der vierte ist technisch der spannendste — Anfragen lassen sich stellen, ohne dass ein Server startet. Wie das geht und warum es so viel Zeit spart, sehen wir gleich im nächsten Kapitel. Der erste Punkt ist der häufigste Grund für Testsuiten, die niemand mehr laufen lässt.

3:05 Wenn jeder Test die ganze Anwendung startet, dauert der Durchlauf Minuten statt Sekunden — und was Minuten dauert, wird vor dem Einchecken übersprungen. Der zweite ist subtiler und betrifft die Aussagekraft: Wer Attrappen so weit konfiguriert, dass sie genau das zurückgeben, was der Test erwartet, prüft am Ende nur noch die eigene Testkonfiguration.

3:25 Der dritte ist harmlos — man ergänzt Testbibliotheken, die der Test-Starter längst mitbringt.

Die Webschicht prüfen

3:31 Fangen wir bei den Controllern an. Der Zuschnitt dafür ist besonders schmal, und genau deshalb sind diese Tests so schnell, dass man sie beim Tippen laufen lassen kann. Radwerk hat den üblichen Schichtenaufbau, und der ist hier ein Vorteil: Controller oben, Repositories unten, klar getrennt. Für die Controller gibt es einen eigenen Zuschnitt.

3:51 Was er tut, ist bewusst wenig — er lädt den einen Controller, den Sie angeben, und die Testfassungen der Web-MVC-Komponenten. Sonst nichts. Keine Repositories, keine Datenbank, keine anderen Controller. Das ist der springende Punkt: Sie prüfen die Webschicht, also alles, was mit Zuordnung, Bindung und Umwandlung zu tun hat, und blenden alles andere aus.

4:14 Drei Zeilen tragen diese Testklasse. Die Annotation an der Klasse benennt den zu prüfenden Controller und setzt damit den Zuschnitt. Das erste Feld ist das Werkzeug, mit dem gleich Anfragen gestellt werden. Und das zweite ist die Attrappe: Die Annotation legt eine Mockito-Attrappe an und registriert sie im Testkontext, sodass der Controller sie statt des echten Repositories bekommt.

4:35 Damit haben Sie die volle Kontrolle darüber, was das Repository liefert — auch über Fälle, die Sie mit echten Daten nur mühsam herstellen könnten, etwa ein leeres Ergebnis. Der Ablauf hier liest sich fast wie ein Satz: Wenn das Repository so antwortet, dann soll die Anfrage auf diesen Pfad zu dieser Antwort führen. Zuerst wird die Attrappe eingestellt, dann die Anfrage gestellt, dann geprüft.

4:58 Worauf es ankommt, ist die letzte Prüfung: Statt den ganzen Antwortrumpf mit einem erwarteten Text zu vergleichen, greifen wir gezielt ein einzelnes Feld heraus. Das ist deutlich robuster — ein zusätzliches Feld in der Antwort bricht den Test nicht. Vergleiche über den kompletten Rumpf sind bequem zu schreiben und danach jahrelang lästig.

5:17 Diese vier Punkte erklären zusammen einen einzigen Trick. Es fließt keine echte HTTP-Anfrage. An die Stelle des echten Servlets tritt eine Testfassung, und das Werkzeug ruft diese direkt auf — mit einem nachgebildeten Anfrageobjekt. Es gibt also keinen Tomcat, keinen Port, keine Netzwerkverbindung und keine Datenbank. Übrig bleibt genau das, was Sie prüfen wollen: die Zuordnung, die Bindung der Parameter, die Umwandlung der Antwort.

5:44 Das ist der Grund, warum solche Tests in Millisekunden laufen und warum man Hunderte davon haben kann, ohne dass es weh tut. Der erste Punkt ist eine Lesbarkeitsfrage, die sich rächt: Testdaten umständlich inline aufzubauen, macht aus zehn Zeilen Prüfung dreißig Zeilen Vorbereitung. Ziehen Sie das in Hilfsmethoden. Der zweite ist ein Missverständnis über den Zuschnitt — wer erwartet, dass die Repositories mit initialisiert werden, bekommt beim Start des Tests eine Fehlermeldung über eine fehlende Abhängigkeit.

6:15 Das ist Absicht. Und der dritte ist eine Prüflücke, die man häufig sieht: Nur den Statuscode zu prüfen, sagt wenig. Ein Endpunkt kann mit Status zweihundert antworten und trotzdem Unsinn liefern.

Die Datenzugriffsschicht prüfen

6:27 Jetzt die andere Seite. Hier geht es um die Repositories, und diesmal drehen wir das Verhältnis um: Es gibt eine echte Datenbank, dafür kein Web. Der Zuschnitt für Spring-Data-JPA-Repositories richtet alles ein, was solche Repositories brauchen — eine H2-Datenbank im Speicher und eine passende Datenquelle. Dazu kommt ein Werkzeug, mit dem sich Testdaten direkt in die Datenbank schreiben lassen, ohne den Umweg über das Repository.

6:53 Das ist wichtiger, als es klingt: Wenn Sie das zu prüfende Repository auch zum Anlegen der Testdaten verwenden, prüfen Sie im Zweifel einen Fehler gegen sich selbst. Die Trennung von Vorbereitung und Prüfung ist bei Datenzugriffstests besonders wertvoll. Die Fußzeile nennt den Grund, warum dieser Test überhaupt existiert.

7:12 Geprüft wird die selbst deklarierte Suchmethode — also genau die, deren Implementierung Spring Data aus dem Namen ableitet. Die geerbten Standardmethoden brauchen Sie nicht zu testen; die sind Teil des Frameworks und anderswo geprüft. Ihre abgeleiteten Methoden dagegen sind Ihre Konstruktion. Der Name drückt eine Absicht aus, und ob das Framework diese Absicht so versteht wie Sie, ist genau die Frage, die dieser Test beantwortet.

7:37 Deshalb lohnt er sich, obwohl Sie keine Zeile Implementierung geschrieben haben. Der Mechanismus dahinter ist elegant: Jede Testmethode läuft in einer eigenen Transaktion, und diese wird danach zurückgerollt. Was ein Test in die Datenbank schreibt, ist für den nächsten nicht mehr da. Damit beginnt jeder Test auf demselben bekannten Stand, unabhängig von der Reihenfolge.

7:59 Das ist eine der Eigenschaften, die man erst schätzt, wenn man sie einmal nicht hatte — Tests, die einzeln laufen und gemeinsam scheitern, gehören zu den unangenehmsten Fehlerbildern überhaupt. Der letzte Punkt zeigt wieder, wie konsistent das Ganze ist: Für MongoDB gilt dasselbe Muster mit einer eigenen Annotation. Bei dieser Aufgabe ist der Hinweis am Ende der eigentliche Erkenntnisgewinn: Vergleichen Sie die Laufzeiten.

8:23 Der Webschichttest wird um ein Vielfaches schneller sein als der Datenzugriffstest, weil dort eine Datenbank hochfährt. Und dieser Unterschied ist der Grund, warum es überhaupt verschiedene Zuschnitte gibt. Formulieren Sie außerdem für jeden Test, was er bewusst nicht berührt. Diese Frage klingt akademisch, ist aber praktisch: Wer sie beantworten kann, wählt beim nächsten Test den richtigen Zuschnitt, statt vorsichtshalber den größten zu nehmen.

8:49 Der erste Punkt ist die Todsünde bei Datenzugriffstests: Testdaten zwischen Methoden weiterreichen und sich auf die Reihenfolge verlassen. Das funktioniert genau so lange, bis jemand einen Test einfügt. Der zweite betrifft die Auswahl — die geerbten Standardmethoden zu testen statt der eigenen Abfragen, ist Aufwand ohne Aussage.

9:08 Und der dritte ist eine Erwartungsfrage: Dieser Zuschnitt richtet eine Testdatenbank im Speicher ein. Wenn Ihre Anwendung in Produktion PostgreSQL nutzt, prüfen Sie hier eine andere Datenbank. Genau dieses Problem löst das letzte Kapitel dieses Moduls.

Integrationstests

9:24 Bis hier haben wir Schichten einzeln geprüft. Jetzt kommt der Test, der alles zusammenspielen lässt — mit echtem Server, echtem Port und echten Repositories. Diese Testklasse gab es schon, seit der Initializr das Projekt erzeugt hat, und die meisten Leute löschen sie ungelesen. Dabei prüft ihre leere Methode etwas durchaus Nützliches: ob sich der Anwendungskontext überhaupt aufbauen lässt.

9:47 Sie schlägt fehl, sobald eine Komponente eine Abhängigkeit erwartet, für die es keinen Bean gibt — also genau bei dem Fehler, den wir in Modul eins bei der Komponentensuche besprochen haben. Für einen leeren Test ist das eine erstaunlich hohe Trefferquote. Lassen Sie sie ruhig stehen, bis Sie etwas Besseres haben. Jetzt der entscheidende Schalter. Standardmäßig entsteht auch bei diesem Zuschnitt nur eine nachgebildete Webumgebung — es läuft also kein Server.

10:14 Mit der Einstellung auf einen zufälligen Port ändert sich das: Tomcat startet tatsächlich und lauscht auf einem freien Port. Warum zufällig? Weil ein fester Port belegt sein kann, etwa weil noch eine Anwendung läuft, und Tests nicht daran scheitern sollen. Weil jetzt echte Anfragen fließen, brauchen wir auch einen anderen Client — das nachgebildete Werkzeug aus dem Webschichttest passt hier nicht mehr.

10:37 Drei Annotationen, drei Aussagen: volle Anwendung mit echtem Port, ein passender HTTP-Client, und ein frischer Kontext nach dieser Klasse. Die Fußzeile nennt den wichtigsten Unterschied zu vorhin — die Repositories sind hier die echten. Keine Attrappen. Das ist der ganze Sinn: Wir wollen wissen, ob Controller, Repositories und Datenbank zusammen das Richtige tun.

10:59 Deshalb sind solche Tests auch so wertvoll, obwohl sie länger dauern. Sie prüfen genau die Übergänge zwischen den Schichten, und dort sitzen erfahrungsgemäß die Fehler, die einzelne Schichttests nicht finden. Diese vier Punkte beschreiben einen Fehler, der ohne Erklärung völlig rätselhaft wirkt. Spring hält Testkontexte vor und verwendet sie mehrfach, weil das Hochfahren teuer ist — an sich eine gute Optimierung. Nur bleibt dabei auch die H2-Datenbank im Speicher bestehen.

11:27 Und wenn zwei Testklassen jeweils ihre Tabellen anlegen wollen, scheitert die zweite mit der Meldung, dass die Tabelle schon existiert. Das Tückische: Welche der beiden scheitert, hängt von der Reihenfolge ab. Die Annotation erzwingt einen frischen Kontext nach jeder Klasse und beseitigt das Problem an der Wurzel. Der erste Punkt kostet Sie im Zweifel eine Stunde: Man setzt die Annotation für den vollen Test, vergisst aber die Webumgebung, und der Test läuft gegen eine Attrappe statt gegen einen echten Server.

11:57 Er ist dann grün und prüft weniger, als man denkt. Der zweite hängt damit zusammen — der Client muss zum gewählten Modus passen. Und der dritte beschreibt die Fehlersuche, die in die Irre führt: Bei der Meldung über eine bereits existierende Tabelle verdächtigt man die Datenbank oder das Schema. Die Ursache ist aber der geteilte Kontext.

Testcontainers für externe Systeme

12:16 Bleibt die Lücke aus dem dritten Kapitel: Wir testen gegen H2, betreiben aber etwas anderes. Testcontainers schließt genau diese Lücke — und tut das erstaunlich elegant. Das Problem ist schnell umrissen. Eine Datenbank im Speicher ist bequem, aber sie ist nicht die Datenbank, die später läuft. Unterschiede in SQL-Dialekt, in Datentypen oder im Verhalten bei Nebenläufigkeit fallen dann erst in Produktion auf.

12:42 Testcontainers dreht das um: Es startet während des Tests einen Docker-Container mit genau der Software, die Sie brauchen, und beendet ihn danach selbsttätig. Damit testen Sie gegen dieselbe Datenbanktechnik, die später wirklich läuft — ohne dass jemand sie dauerhaft installieren muss. Der Preis ist Laufzeit und eine Docker-Installation.

13:02 Der rote Faden: Der Test bringt seine Umgebung selbst mit. Nach den Abhängigkeiten und den bekannten Annotationen ist der eigentliche Kniff das Feld für den Container. Zwei Annotationen daran erledigen zwei verschiedene Dinge, wie die Fußzeile erklärt. Die eine meldet das Feld der JUnit-Erweiterung, die den Container startet und stoppt.

13:22 Die andere meldet Spring Boot, dass hier ein externer Dienst beschrieben ist, und übergibt dessen Verbindungsdaten an den Anwendungskontext. Ohne die zweite läuft zwar der Container, aber Ihre Anwendung weiß nichts davon — ein Fehlerbild, das zunächst verwirrt. Der Test selbst folgt dem bekannten Muster: über HTTP anlegen, anschließend im Repository nachsehen.

13:43 Interessant ist der Blick ins Protokoll, wo Sie Start und Stopp des Containers sehen. Und der Hinweis am Ende ist ernst gemeint: Starten Sie den Test einmal ohne laufendes Docker und schauen Sie sich die Fehlermeldung genau an. Sie werden ihr wiederbegegnen — bei einem Kollegen mit anderem Rechner, in einer Bauumgebung ohne Docker, auf einem verwalteten Firmenrechner.

14:04 Wer die Meldung kennt, spart sich dann die halbe Diagnose. Der erste Punkt ist die vergessene zweite Annotation von vorhin, und das Fehlerbild ist deshalb tückisch, weil der Container ja läuft. Der zweite betrifft die Wiederholbarkeit: Ein Container startet leer, aber wenn mehrere Tests denselben verwenden, sammeln sich Daten an, und plötzlich hängt das Ergebnis von der Reihenfolge ab.

14:27 Deshalb der Aufräumschritt vor jedem Test. Und der dritte ist eine Frage des Maßes: Testcontainers ist mächtig und verführt dazu, es überall einzusetzen. Wo eine Attrappe die Frage schneller beantwortet, ist sie die bessere Wahl.

Was Sie aus Modul 5 mitnehmen

14:40 Der erste Satz ist der Kern dieses Moduls: Der Zuschnitt entscheidet über Aussagekraft und Laufzeit, und beides zieht in entgegengesetzte Richtungen. Die schmalen Zuschnitte prüfen je eine Schicht und laufen in Millisekunden; davon dürfen Sie viele haben. Der volle Test mit zufälligem Port prüft das echte Zusammenspiel und ist deshalb wertvoll, aber teuer; davon nehmen Sie wenige und gezielt.

15:02 Und Testcontainers bringt das externe System für die Dauer des Tests mit, wenn die Nähe zur Produktion es rechtfertigt. Im nächsten Modul geht es darum, was passiert, wenn die Anwendung dann tatsächlich läuft.

Lieber mit Trainer? Dieses Modul ist Teil unserer Team-Schulungen — mit Übungen, Ihrem eigenen Code und Fragen, die ein Video nicht beantwortet. Mehr erfahren →