Room Testing: DAOs und Migrationen sicher testen
Mit In-Memory-Datenbanken testest du Room-DAOs und Schemamigrationen isoliert und schnell. So erkennst du Datenbankfehler früh.
Room ist Androids offizielle Abstraktionsschicht über SQLite – und wie jede Datenbankschicht kann sie still brechen, wenn sich Queries oder das Schema ändern. Room Testing gibt dir die Werkzeuge, um genau das zu verhindern: automatisierte Tests für DAOs und Migrationen, die auf der JVM laufen und innerhalb von Sekunden Feedback liefern.
Was ist das?
Room Testing bezeichnet das gezielte Absichern deiner Room-Datenbankschicht durch automatisierte Tests auf drei Ebenen: einzelne DAO-Methoden, das Datenbankschema als Ganzes und die Migrationspfade zwischen Schemaversionen.
Das Herzstück ist die In-Memory-Datenbank. Statt eine echte SQLite-Datei auf dem Gerät zu öffnen, wird eine flüchtige Datenbank vollständig im Arbeitsspeicher angelegt. Sie existiert nur für die Dauer eines Tests und wird danach automatisch verworfen. Das Ergebnis: Tests sind deterministisch, vollständig isoliert und laufen ohne Emulator.
Room-Tests gehören technisch zu den Instrumentierungstests unter androidTest/ – sie benötigen eine echte SQLite-Engine. Da aber kein Gerätezustand benötigt wird, lassen sie sich mit Robolectric oder direkt auf physischen Geräten und CI-Agenten in überschaubarer Zeit ausführen. Die zugehörigen Bibliotheken sind androidx.room:room-testing sowie androidx.room:room-ktx.
Wie funktioniert es?
In-Memory-Datenbank einrichten
Der Einstiegspunkt ist Room.inMemoryDatabaseBuilder(). Im Vergleich zu databaseBuilder() schreibt er nichts auf die Disk und erlaubt mit allowMainThreadQueries() synchrone Abfragen – was in Tests praktisch ist, in Produktionscode aber verboten bleibt:
@get:Rule
val db: AppDatabase = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
AppDatabase::class.java
).allowMainThreadQueries().build()
Schließe die Datenbank in @After wieder, damit keine Ressourcen offen bleiben:
@After
fun closeDb() = db.close()
DAOs testen
Ein DAO-Test folgt immer demselben Muster: schreiben, lesen, assertieren. Nutze runBlocking {} für suspend-Funktionen oder kotlinx-coroutines-test für strukturiertes Coroutine-Testing:
@Test
fun insertAndRetrieveUser() = runBlocking {
val user = User(id = 1, name = "Ada Lovelace")
db.userDao().insert(user)
val loaded = db.userDao().getUserById(1)
assertThat(loaded).isEqualTo(user)
}
Teste nicht nur den Erfolgsfall. Prüfe auch:
null-Rückgaben bei fehlenden Einträgen- Verhalten bei
UNIQUE-Constraint-Verletzungen - Korrekte Sortierung und Filterlogik in
@Query-Annotationen
Migrationen testen
Migrationen sind der fehleranfälligste Teil von Room, weil Tippfehler in SQL-DDL erst zur Laufzeit auffallen. MigrationTestHelper schließt diese Lücke. Er öffnet die Datenbank in der Quellversion, führt deine Migration aus und vergleicht das resultierende Schema automatisch mit der exportierten JSON-Datei der Zielversion:
@get:Rule
val helper: MigrationTestHelper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java
)
@Test
fun migrate1To2() {
// Datenbank in Version 1 anlegen
helper.createDatabase(TEST_DB, 1).close()
// Migration ausführen und Schema validieren
val db = helper.runMigrationsAndValidate(
TEST_DB, 2, true, MIGRATION_1_2
)
// Optional: Vorhandene Datensätze aus Version 1 prüfen
val cursor = db.query("SELECT * FROM users", null)
assertThat(cursor.count).isEqualTo(0)
db.close()
}
In der Praxis
Stolperfalle: fehlendes Schema-Export-Setup
Der häufigste Fehler ist, roomSchemaLocation in build.gradle.kts zu vergessen. Ohne diesen Eintrag exportiert Room kein Schema-JSON, und MigrationTestHelper.runMigrationsAndValidate() wirft zur Laufzeit eine Exception – obwohl der Code problemlos kompiliert.
Füge in build.gradle.kts Folgendes hinzu:
android {
defaultConfig {
ksp {
arg("room.schemaLocation", "$projectDir/schemas")
}
}
}
Bei älteren Projekten mit KAPT ersetzt du ksp { arg(...) } durch den entsprechenden annotationProcessorOptions-Block. Checke anschließend den schemas/-Ordner ins Repository ein – er ist der Vertrag für alle zukünftigen Migrationen und muss versioniert werden.
Entscheidungsregel: Was gehört in einen DAO-Test?
Schreibe mindestens einen Test pro @Query-Annotation, die eine Filterbedingung, Sortierung, einen Join oder eine Aggregation enthält. Reine Einfüge- und Löschoperationen ohne Logik kannst du weglassen, wenn das Schema bereits durch Migrationstests abgedeckt ist. Queries, die direkt in UI-State oder Business-Logik einfließen, verdienen immer einen eigenen Test – sie sind die Stellen, an denen stille Fehler die meisten Nutzer treffen.
Stolperfalle: Datenintegrität nach Migrationen nicht geprüft
MigrationTestHelper.runMigrationsAndValidate() prüft nur das Schema, nicht die Daten. Wenn deine Migration eine Spalte umbenennt oder einen Standardwert setzt, musst du die Datensätze manuell über einen Cursor abfragen und assertieren – sonst bekommst du eine grüne Schemavalidierung bei gleichzeitig korrumpierten Daten.
Fazit
Room Testing ist keine optionale Zugabe, sondern die einzige verlässliche Methode, um sicherzustellen, dass deine Datenbankschicht über App-Versionen hinweg stabil bleibt. Lege noch heute in deinem Projekt einen inMemoryDatabaseBuilder-Test für die wichtigste DAO-Methode an, konfiguriere danach roomSchemaLocation und füge MigrationTestHelper-Tests für jede bestehende Migration hinzu. Wenn du die CI-Pipeline so einrichtest, dass diese Tests bei jedem Pull Request laufen, wirst du Datenbankfehler konsequent erkennen, bevor sie deine Nutzer erreichen.