Assertions – Erwartetes Verhalten klar formulieren
Assertions machen Testversprechen explizit. Sie zeigen beim Fehlschlag genau, was erwartet wurde und was stattdessen ankam.
Wer einen Test schreibt, hat immer eine stille Erwartung im Kopf: „Diese Methode soll genau diesen Wert zurückgeben” oder „Nach dieser Aktion soll die Liste leer sein.” Assertions übersetzen diese gedachten Garantien in ausführbaren Code. Das klingt trivial, ist aber der entscheidende Unterschied zwischen einem Test, der nur läuft, und einem Test, der auch etwas aussagt.
Was ist das?
Eine Assertion ist eine programmatische Behauptung darüber, was zu einem bestimmten Zeitpunkt wahr sein muss. Erfüllt der Code die Bedingung nicht, schlägt der Test fehl und gibt eine Fehlermeldung aus, die beschreibt, was erwartet wurde und was stattdessen ankam.
Im Android-Umfeld begegnen dir Assertions in vielen Formen: in JUnit-5-Unit-Tests für ViewModels und Use Cases, in Compose-UI-Tests mit onNode().assert(…), in Espresso-Instrumentierungstests auf Gerät oder Emulator und in Coroutine-Tests mit Turbine. Unabhängig vom Framework bleibt das Grundprinzip gleich: Du formulierst eine Erwartung, der Test-Runner prüft sie zur Laufzeit.
Der Begriff hat seine Wurzeln in „Design by Contract” – der Idee, dass Code Vor- und Nachbedingungen für sein Verhalten garantiert. Für die Praxis reicht ein einfacheres Bild: Eine Assertion ist ein Sicherheitsnetz, das nicht nur meldet, wann es gerissen ist, sondern auch genau zeigt, an welcher Stelle.
Wie funktioniert es?
JUnit 5, das Standard-Framework für Android-Unit-Tests, bietet die Klasse Assertions mit Methoden wie assertEquals, assertNotNull, assertTrue, assertThrows und dem besonders nützlichen assertAll, das mehrere Assertions in einem Durchlauf sammelt statt beim ersten Fehlschlag abzubrechen.
Daneben hat Google die Bibliothek Truth entwickelt, die eine fließende API nach dem Prinzip „Subjekt – Verb – Erwarteter Wert” bereitstellt:
// JUnit 5 – klassisch
assertEquals(42, result)
assertNotNull(user)
// Truth – liest sich wie ein Satz
assertThat(result).isEqualTo(42)
assertThat(user).isNotNull()
assertThat(list).containsExactly("a", "b", "c").inOrder()
Truth ist in Android-Projekten besonders verbreitet, weil die Fehlermeldungen deutlich aussagekräftiger sind. Statt expected: <42> but was: <0> liest du eine vollständige Beschreibung des tatsächlichen Zustands, die sofort auf das Problem zeigt.
Fehler prüfen mit assertThrows
Assertions sind nicht nur für Erfolgsszenarien gedacht. assertThrows in JUnit 5 und assertFailsWith in Kotlin-Tests prüfen, ob der richtige Fehler zur richtigen Zeit ausgelöst wird:
assertFailsWith<IllegalArgumentException> {
parseUserId("")
}
Das ist erwartetes Verhalten, klar formuliert: Nicht „es stürzt nicht ab”, sondern „es wirft genau diesen Fehlertyp”. Solche Assertions gehören genauso zur Qualitätssicherung wie positive Prüfungen.
In der Praxis
Betrachte einen ViewModel-Test. Ein LoginViewModel soll bei leerem Passwort den UI-State auf Error setzen:
@Test
fun `empty password sets error state`() {
val viewModel = LoginViewModel(FakeAuthRepository())
viewModel.onPasswordChanged("")
viewModel.onLoginClicked()
val state = viewModel.uiState.value
assertThat(state).isInstanceOf(LoginUiState.Error::class.java)
assertThat((state as LoginUiState.Error).message)
.contains("Passwort")
}
Die erste Assertion prüft den Typ, die zweite den Inhalt. Zwei gezielte Checks sind besser als ein einzelnes assertEquals(expectedState, state), weil du beim Fehlschlag sofort siehst, welche Dimension das Problem hat – der Typ oder der Text.
Häufige Stolperfalle: zu vage assertions. Eine Assertion wie assertThat(result).isNotNull() ist fast wertlos, wenn du eigentlich prüfen willst, ob ein bestimmter Wert zurückkommt. Sei konkret:
// Zu vage – besteht auch bei falschem Wert
assertThat(result).isNotNull()
// Besser – prüft das tatsächliche Verhalten
assertThat(result.userId).isEqualTo("user-123")
Zweite Stolperfalle: Implementierungsdetails statt Verhalten. Wenn du prüfst, wie oft eine interne Methode aufgerufen wurde, testest du Interna – nicht Verhalten. Beim nächsten Refactoring bricht der Test, obwohl der Code korrekt ist. Prüfe stattdessen beobachtbare Outputs: Rückgabewerte, State-Änderungen, emittierte Flow-Events.
Fazit
Assertions sind das Sprachrohr deiner Tests: Sie sagen laut, was du erwartest, und melden unmissverständlich, wenn diese Erwartung verletzt wird. Öffne einen deiner bestehenden Tests und lies die Assertions wie Sätze – klingen sie wie Verhaltensversprechen oder wie interne Implementierungschecks? Refaktoriere einen Test mit Truth, falls du noch pure JUnit-5-Assertions verwendest, und vergleiche die Fehlermeldungen beim nächsten Fehlschlag. Dieses kleine Experiment schärft den Blick dafür, was eine gute Assertion leisten soll: Verhalten dokumentieren, Fehler lokalisieren und Regressionen verhindern, bevor sie in Production landen.