Serialization Testing in Android
Teste JSON-Parsen mit echten Beispielen. So bleiben App-Daten bei API-Änderungen kompatibel.
Serialization Testing bedeutet, dass du gezielt prüfst, ob deine App Daten korrekt zwischen JSON und Kotlin-Objekten übersetzt. Für Android ist das besonders wichtig, weil viele Fehler nicht in der Compose-Oberfläche entstehen, sondern schon vorher: in der Data Layer, beim Parsen einer API-Antwort, beim Speichern im Cache oder beim Lesen eines fehlenden Feldes.
Was ist das?
Serialization ist der Vorgang, bei dem strukturierte Daten in ein transportierbares Format umgewandelt werden. In Android-Apps ist dieses Format sehr oft JSON. Deserialization ist der Rückweg: Aus JSON wird wieder ein Kotlin-Objekt, etwa ein UserDto, ArticleDto oder ProductResponse. Serialization Testing prüft diese Übersetzung mit Tests, bevor echte Nutzer davon betroffen sind.
Der wichtigste Gedanke für dich ist: Eine API-Antwort ist kein Versprechen, dass immer alles exakt so bleibt. Felder können fehlen, neue Felder können dazukommen, ein Wert kann null sein, oder ein Backend liefert bei alten App-Versionen eine leicht andere Struktur. Deine App muss solche Fälle bewusst behandeln. Sonst bekommst du Abstürze, leere Screens oder falsche Daten in deinem Offline-Cache.
Im Android-Kontext sitzt dieses Thema meist in der Data Layer. Dort werden Netzwerkmodelle, Mapper, Repositories und lokale Datenquellen koordiniert. Deine Compose-UI sollte sich nicht darum kümmern müssen, ob ein JSON-Feld fehlt. Sie sollte einen klaren UI-State erhalten. Genau deshalb testest du die Serialisierung nah an den DTOs und Mappern. So erkennst du früh, ob deine App mit realistischen API-Beispielen kompatibel bleibt.
JSON-Fixtures sind dabei gespeicherte Beispielantworten. Du legst sie als Testdaten ab und verwendest sie in Unit-Tests. Eine Fixture sollte möglichst so aussehen wie eine echte Antwort deines Backends. Nicht hübsch verkürzt, nicht frei erfunden, sondern nah an der Realität. Dadurch testest du nicht nur deine Wunschstruktur, sondern die Datenform, die deine App im Alltag wirklich sieht.
Wie funktioniert es?
Ein guter Serialization-Test folgt einem einfachen Ablauf. Zuerst nimmst du eine JSON-Fixture. Dann lässt du deinen JSON-Parser daraus ein Kotlin-Modell erzeugen. Danach prüfst du die relevanten Felder. Zusätzlich baust du Varianten ein: ein vollständiges Beispiel, ein Beispiel mit fehlendem optionalem Feld und ein Beispiel mit neuen unbekannten Feldern.
Das mentale Modell ist eine Grenze zwischen außen und innen. Außen kommt JSON aus einem System, das du in der App nicht vollständig kontrollierst. Innen möchtest du stabile Kotlin-Typen nutzen. Serialization Testing prüft diese Grenze. Es beantwortet Fragen wie: Kann mein Parser die Antwort lesen? Sind optionale Felder wirklich optional? Haben Default-Werte den erwarteten Effekt? Ignoriert die App unbekannte Felder, wenn das gewollt ist? Wird ein Pflichtfeld klar als Fehler erkannt?
In Kotlin arbeitest du häufig mit Libraries wie kotlinx.serialization, Moshi oder Gson. Welche Library du nutzt, ist für die Grundidee zweitrangig. Wichtig ist, dass du die Regeln deiner Library kennst. Manche Parser verlangen Default-Werte, andere brauchen Annotationen, wieder andere verhalten sich bei unbekannten Feldern anders. Ein Test schützt dich davor, diese Details nur aus dem Gedächtnis zu behandeln.
Für moderne Android-Architektur heißt das: DTOs sind nicht automatisch deine Domain-Modelle. Ein ArticleDto kann nullable Felder enthalten, weil JSON unsicher ist. Ein Article in deiner Domain sollte dagegen möglichst klare Regeln ausdrücken. Der Mapper zwischen beiden ist ein guter Ort, um Entscheidungen zu treffen: Was passiert, wenn subtitle fehlt? Was passiert, wenn publishedAt leer ist? Wird ein Artikel verworfen, oder bekommt er einen Fallback?
Serialization Testing unterstützt auch Offline-First-Ansätze. Wenn du Daten aus dem Netzwerk lädst und lokal speicherst, kann ein Parser-Fehler verhindern, dass dein Cache aktualisiert wird. Noch unangenehmer ist ein stiller Fehler: Die App stürzt nicht ab, speichert aber unvollständige oder falsch gemappte Daten. Tests mit realistischen Fixtures helfen dir, diese Fehler zu finden, bevor sie in der Qualitätssicherung oder im Release sichtbar werden.
Wichtig ist die Trennung der Testarten. Für Serialization Testing brauchst du in vielen Fällen keinen Emulator und keine Instrumentation. Ein schneller Unit-Test auf der JVM reicht oft aus. Du testest nicht Android-UI, Navigation oder Datenbankverhalten, sondern die reine Umwandlung von Text zu Modell und von Modell zu stabiler App-Struktur. Dadurch laufen diese Tests schnell und eignen sich gut für jede Pull Request-Prüfung.
In der Praxis
Stell dir eine App vor, die Artikel von einer API lädt. Das Backend liefert JSON. In Version 1 enthält jeder Artikel id, title und summary. Später wird summary bei manchen Artikeln weggelassen. Deine App soll deshalb nicht abstürzen, sondern einen leeren oder generierten Beschreibungstext verwenden.
Eine einfache Fixture könnte so aussehen: Du speicherst im Testressourcen-Ordner eine Datei article_missing_summary.json. Sie enthält einen Artikel ohne summary. Der Test prüft, ob dein DTO gelesen werden kann und ob der Mapper korrekt reagiert.
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
@Serializable
data class ArticleDto(
val id: String,
val title: String,
val summary: String? = null
)
data class Article(
val id: String,
val title: String,
val summary: String
)
fun ArticleDto.toDomain(): Article {
return Article(
id = id,
title = title,
summary = summary ?: "Keine Zusammenfassung verfügbar"
)
}
class ArticleSerializationTest {
private val json = Json {
ignoreUnknownKeys = true
}
@Test
fun `article without summary can be parsed and mapped`() {
val fixture = """
{
"id": "a-42",
"title": "Kotlin auf Android",
"authorName": "Redaktion"
}
""".trimIndent()
val dto = json.decodeFromString<ArticleDto>(fixture)
val article = dto.toDomain()
assertNotNull(dto)
assertEquals("a-42", article.id)
assertEquals("Kotlin auf Android", article.title)
assertEquals("Keine Zusammenfassung verfügbar", article.summary)
}
}
Dieser Test zeigt mehrere wichtige Punkte. summary ist im DTO nullable und hat einen Default-Wert. Dadurch kann die JSON-Antwort ohne dieses Feld gelesen werden. Im Domain-Modell ist summary dagegen nicht nullable, weil die restliche App stabiler mit einem konkreten String arbeiten kann. Die Entscheidung über den Fallback liegt im Mapper, nicht in der UI.
Die Option ignoreUnknownKeys = true ist ebenfalls eine Kompatibilitätsentscheidung. Im Beispiel enthält JSON ein Feld authorName, das im DTO nicht definiert ist. Wenn deine App unbekannte Felder ignoriert, kann das Backend neue Informationen ausliefern, ohne alte App-Versionen direkt zu brechen. Das ist besonders wichtig, wenn Nutzer App-Updates nicht sofort installieren.
Eine praktische Regel: Teste mindestens eine vollständige Fixture und eine reduzierte Fixture pro wichtiger API-Antwort. Die vollständige Fixture zeigt, dass der normale Pfad funktioniert. Die reduzierte Fixture zeigt, dass die App mit fehlenden optionalen Feldern umgehen kann. Bei kritischen Daten, etwa Preisen, IDs, Berechtigungen oder Zahlungsstatus, solltest du außerdem prüfen, dass fehlende Pflichtfelder nicht still akzeptiert werden.
Eine typische Stolperfalle ist, Test-JSON zu klein und zu sauber zu machen. Wenn deine Fixture nur drei Felder enthält, die exakt zu deinem DTO passen, testest du vor allem deine eigene Annahme. Eine echte API-Antwort enthält oft verschachtelte Objekte, Listen, neue Felder, leere Strings, null, unterschiedliche Reihenfolgen und manchmal Werte, die fachlich ungewöhnlich sind. Gute Fixtures dürfen etwas unordentlich wirken, solange sie realistisch sind.
Eine zweite Stolperfalle ist, DTOs direkt in der Compose-UI zu verwenden. Das wirkt am Anfang bequem, koppelt deine Oberfläche aber an die Netzwerkstruktur. Wenn später ein Feld optional wird, musst du plötzlich UI-Code reparieren, obwohl das Problem in der Data Layer gelöst werden sollte. Besser ist: JSON wird in DTOs gelesen, DTOs werden in Domain-Modelle gemappt, und Compose rendert einen klaren Zustand.
Auch Code-Reviews werden durch Serialization Tests besser. Du kannst bei einer Änderung am DTO konkret fragen: Gibt es eine Fixture für die neue Struktur? Was passiert mit alten Antworten? Wird ein neues Feld wirklich benötigt, oder ist es optional? Gibt es einen Test für fehlende Daten? Solche Fragen sind sachlicher als eine allgemeine Diskussion über Stil.
Zum Üben kannst du eine bestehende API-Antwort aus einem Projekt nehmen und daraus drei Fixtures bauen: normal, mit fehlendem optionalem Feld und mit zusätzlichem unbekanntem Feld. Schreibe dann Tests, die jeweils nur die wichtigsten fachlichen Aussagen prüfen. Vermeide Tests, die jedes einzelne Feld stumpf vergleichen. Prüfe lieber die Regeln, die deine App stabil halten.
Fazit
Serialization Testing ist ein kleiner, aber sehr wirksamer Teil professioneller Android-Entwicklung. Du schützt damit die Grenze zwischen externer JSON-Welt und interner Kotlin-Struktur. Arbeite mit realistischen JSON-Fixtures, teste fehlende Felder bewusst und halte Kompatibilitätsentscheidungen in der Data Layer. Prüfe dein Verständnis praktisch: Ändere eine Fixture, entferne ein Feld, starte den Test und beobachte im Debugger, ob DTO, Mapper und Domain-Modell so reagieren, wie du es fachlich erwartest.