Problem

Kotlin Flows sind aus der Android-Entwicklung nicht mehr wegzudenken. Mit ihrer Hilfe können Daten sequenziell über Datenströme gesendet werden. Diese Daten können dann im Code asynchron abgefragt werden. Natürlich müssen Flows auch getestet werden – leider ist das in Android nicht ganz so einfach.

Wenn man beim Implementieren der Tests nicht aufpasst, muss man mit Fehlern wie „This Job has not completed yet“ oder „UncompletedCoroutinesError“ kämpfen. Wer mit diesen Fehlern zu tun hatte, weiß, wie aufwändig eine Fehlersuche sein kann, um sie zu beheben. Die Tests enthalten außerdem viel Boilerplate-Code, der die Komplexität zusätzlich erhöht und die Lesbarkeit verschlechtert.

Lösung

Cashapp hat eine kleine Library für das Testen von Flows veröffentlicht: Turbine. Sie bietet zahlreiche Hilfsfunktionen, die das Testen von Flows deutlich bequemer machen.

Eine davon ist die test-Extension-Function. Sie startet eine neue Coroutine, ruft collect() auf und schickt die Daten an Turbine. Zum Schluss wird der Validation-Block aufgerufen.

Wenn der Validation-Block durchgelaufen ist, wird die Coroutine automatisch beendet. Innerhalb des Validation-Blocks können Methoden aufgerufen werden, um die Daten abzurufen und zu validieren.

Beispiel

Hier sind einige Beispiele – es werden jedoch noch viel mehr unterstützt:

  • awaitItem(): Unterbricht die Coroutine und wartet, bis ein Item gesendet wurde
  • awaitError(): Unterbricht die Coroutine und wartet darauf, dass eine Exception geschmissen wird
  • skipItems(n: Int): Ignoriert n Items
  • expectMostRecentItem(): Holt sich alle bisher gesendeten Items und gibt das neueste zurück

Das nachfolgende Beispiel zeigt einen Unit-Test einmal ohne (links) und einmal mit Turbine (rechts). Für das Mocking wurde die Library mockk verwendet.

interface Repository {
    fun items(): SharedFlow<Int>
}
@Test
fun testFlowWithoutTurbine() = runTest {
  val flow = MutableSharedFlow<Int>()
  val repository = mockk<Repository>()
  every { repository.items() }
    returns flow
 
  val dispatcher =
  UnconfinedTestDispatcher(testScheduler)
     
  val values = mutableListOf<Int>()
  backgroundScope.launch(dispatcher) {
    repository.items().toList(values)
  }
 
  flow.emit(1)
  assertEquals(1, values[0])
 
  flow.emit(2)
  flow.emit(3)
  assertEquals(3, values[2])
}
@Test
fun testFlowWithTurbine() = runTest { 
  val flow = MutableSharedFlow<Int>()
  val repository = mockk<Repository>()
  every { repository.items() }
    returns flow
 
  repository.items().test {
    flow.emit(1)
    assertEquals(1, awaitItem())
 
    flow.emit(2)
    skipItems(1)
     
    flow.emit(3)
    assertEquals(3, awaitItem())
  }
}

SCHREIB UNS

* Pflichtfeld

SCHREIB UNS

* Pflichtfeld

Cookie-Einstellungen

Diese Website verwendet Cookies, um Inhalte und Anzeigen zu personalisieren, Funktionen für soziale Medien anbieten zu können und Zugriffe auf die Website zu analysieren. Zudem werden Informationen zu Ihrer Verwendung der Website an Partner für soziale Medien, Werbung und Analysen weitergegeben. Die Partner führen diese Informationen möglicherweise mit weiteren Daten zusammen, die Sie ihnen bereitgestellt haben oder die sie im Rahmen Ihrer Nutzung der Dienste gesammelt haben.

Weitere Informationen finden Sie in unserer Datenschutzerklärung. Dort können Sie nachträglich auch Ihre Cookie-Einstellungen ändern.

contact icon

Kontakt aufnehmen