Einleitung: „Warum fällt mein Test durch, wenn er bestehen sollte?“
\n\nHaben Sie jemals einen Unit-Test geschrieben, der eine Exception abfangen sollte, stattdessen aber mit einem roten Fehler durchgefallen ist? Kommt Ihnen bekannt vor? Die Frage „How do you assert that a certain exception is thrown in JUnit tests?“ auf Stack Overflow hat tausende Aufrufe und hunderte Kommentare gesammelt. Und das ist kein Zufall: Das Testen von Exceptions ist eines der Themen, bei denen selbst erfahrene Entwickler manchmal ins Straucheln geraten.
\n\nIm Jahr 2025, wo Codequalität zu einem kritischen Faktor geworden ist (insbesondere in Microservices und AI-Pipelines), ist die Fähigkeit, Exceptions korrekt zu prüfen, nicht nur „guter Stil“, sondern eine grundlegende Kompetenz. In diesem Artikel analysieren wir alle aktuellen Methoden, um Exceptions in JUnit 5 zu asserten, zeigen Fallstricke auf und geben eine Checkliste mit Best Practices. Los geht‘s!
\n\nWarum ist es wichtig, Exceptions zu testen?
\n\nStellen Sie sich vor: Sie schreiben eine Methode divide(int a, int b). Wenn b == 0 ist, soll die Methode eine ArithmeticException werfen. Ohne einen Test für dieses Verhalten riskieren Sie, dass ein anderer Entwickler (oder Sie selbst) in einem Monat versehentlich die Prüfung entfernt und die Anwendung in der Produktion mit einem unklaren Fehler abstürzt.
Tests auf Exceptions sind Ihre Absicherung. Sie dokumentieren das erwartete Verhalten des Systems in Grenzfällen. Und JUnit 5 bietet dafür mehrere elegante Werkzeuge.
\n\nMethode 1: assertThrows – der König des modernen Testens
\n\nSeit JUnit 5 (und seinem Paket org.junit.jupiter.api) ist die Methode assertThrows der De-facto-Standard. Warum? Weil sie prägnant, lesbar und funktional ist.
import static org.junit.jupiter.api.Assertions.assertThrows;\n\n@Test\nvoid whenDivideByZero_thenArithmeticException() {\n assertThrows(ArithmeticException.class, \n () -> calculator.divide(10, 0));\n}\n\n\nWas passiert hier? assertThrows nimmt die Klasse der erwarteten Exception und ein Lambda mit dem Code, der diese werfen soll. Wird die Exception geworfen – der Test ist grün. Wenn nicht – rot. Alles einfach und anschaulich.
Erweiterte Variante: Die Exception-Nachricht prüfen
\n\nManchmal reicht es nicht, nur den Typ der Exception zu prüfen – man muss sicherstellen, dass die Nachricht den richtigen Text enthält. assertThrows gibt das Exception-Objekt zurück, und Sie können seine Eigenschaften prüfen:
import static org.junit.jupiter.api.Assertions.*;\n\n@Test\nvoid whenInvalidEmail_thenExceptionWithMessage() {\n IllegalArgumentException exception = assertThrows(\n IllegalArgumentException.class,\n () -> userService.register("invalid-email")\n );\n \n assertTrue(exception.getMessage().contains("Ungültige E-Mail"));\n}\n\n\nDieser Ansatz ist besonders nützlich beim Testen von benutzerdefinierten Exceptions mit Feldern (z. B. Fehlercode).
\n\nMethode 2: Der gute alte expected (JUnit 4 Legacy)
\n\nWenn Sie mit JUnit 4 arbeiten oder Legacy-Code warten, sind Sie sicherlich auf das Attribut expected in der Annotation @Test gestoßen:
@Test(expected = ArithmeticException.class)\npublic void whenDivideByZero_thenException() {\n calculator.divide(10, 0);\n}\n\n\nNachteil: Diese Methode erlaubt es nicht, die Exception-Nachricht oder ihre Eigenschaften zu prüfen. Außerdem kann der Test fälschlicherweise bestehen, wenn die Exception früher als erwartet geworfen wird (z. B. in der Initialisierungszeile). Im Jahr 2025 empfehlen wir, diesen Ansatz in neuem Code zu vermeiden.
\n\nMethode 3: Try-catch mit Flag (für Kontroll-Fans)
\n\nVor dem Erscheinen von JUnit 5 verwendeten viele das Konstrukt mit try-catch und einem booleschen Flag. Es sieht umständlich aus, bietet aber volle Kontrolle:
@Test\nvoid whenDivideByZero_thenExceptionCaught() {\n boolean thrown = false;\n try {\n calculator.divide(10, 0);\n } catch (Arithmeti