Google se compromete a impulsar la igualdad racial para las comunidades afrodescendientes. Obtén información al respecto.

Cómo probar flujos de Kotlin

La forma en que pruebas las unidades o los módulos que se comunican con un flujo depende de si el sujeto de prueba usa el flujo como entrada o salida.

  • Si el sujeto de prueba observa un flujo, puedes generar flujos dentro de dependencias falsas que puedes controlar en las pruebas.
  • Si la unidad o el módulo expone un flujo, puedes leer y verificar uno o varios elementos emitidos por el flujo en la prueba.

Crea un productor falso

Cuando el sujeto de prueba es consumidor de un flujo, una forma común de probarlo es reemplazar el productor con una implementación falsa. Por ejemplo, en una determinada clase que observa un repositorio que toma datos de dos fuentes de datos en producción:

el sujeto de prueba y la capa de datos
Figura 1: El sujeto de prueba y la capa de datos

Para que la prueba sea determinante, puedes reemplazar el repositorio y sus dependencias con un repositorio falso que siempre emita los mismos datos falsos:

reemplazo de las dependencias con una implementación falsa
Figura 2: Reemplazo de las dependencias con una implementación falsa

Para emitir una serie predefinida de valores en un flujo, usa el compilador flow:

class MyFakeRepository : MyRepository {
    fun observeCount() = flow {
        emit(ITEM_1)
    }
}

En la prueba, se inserta este repositorio falso para reemplazar la implementación real:

@Test
fun myTest() {
    // Given a class with fake dependencies:
    val sut = MyUnitUnderTest(MyFakeRepository())
    // Trigger and verify
    ...
}

Ahora que tienes el control de los resultados del sujeto de prueba, puedes verificar que funcione correctamente revisando el resultado.

Confirma las emisiones del flujo en una prueba

Si el sujeto de prueba expone un flujo, la prueba debe realizar aserciones sobre los elementos del flujo de datos.

Supongamos que el repositorio del ejemplo anterior expone un flujo:

repositorio con dependencias falsas que expone un flujo
Figura 3: Repositorio (sujeto de prueba) con dependencias falsas que expone un flujo

Según las necesidades de la prueba, por lo general, deberás verificar la primera emisión o una cantidad limitada de elementos provenientes del flujo.

Puedes consumir la primera emisión al flujo llamando a first(). Esta función espera hasta que se reciba el primer elemento y, luego, envía la señal de cancelación al productor.

@Test
fun myRepositoryTest() = runBlocking {
    // Given a repository that combines values from two data sources:
    val repository = MyRepository(fakeSource1, fakeSource2)

    // When the repository emits a value
    val firstItem = repository.counter.first() // Returns the first item in the flow

    // Then check it's the expected item
    assertThat(firstItem, isEqualTo(ITEM_1) // Using AssertJ
}

Si la prueba necesita verificar varios valores, llamar a toList() hace que el flujo espere a que la fuente emita todos sus valores y, luego, los muestra en una lista. Ten en cuenta que esta opción solo funciona con flujos de datos finitos.

@Test
fun myRepositoryTest() = runBlocking {
    // Given a repository with a fake data source that emits ALL_MESSAGES
    val messages = repository.observeChatMessages().toList()

    // When all messages are emitted then they should be ALL_MESSAGES
    assertThat(messages, isEqualTo(ALL_MESSAGES))
}

En el caso de flujos de datos que requieren una recopilación de elementos más compleja o que no muestran una cantidad limitada de elementos, puedes usar la API de Flow para seleccionar y transformar elementos. Estos son algunos ejemplos:

// Take the second item
outputFlow.drop(1).first()

// Take the first 5 items
outputFlow.take(5).toList()

// Take the first 5 distinct items
outputFlow.take(5).toSet()

// Take the first 2 items matching a predicate
outputFlow.takeWhile(predicate).take(2).toList()

// Take the first item that matches the predicate
outputFlow.firstWhile(predicate)

// Take 5 items and apply a transformation to each
outputFlow.map(transformation).take(5)

// Takes the first item verifying that the flow is closed after that
outputFlow.single()

// Finite data streams
// Verify that the flow emits exactly N elements (optional predicate)
outputFlow.count()
outputFlow.count(predicate)

CoroutineDispatcher como dependencia

Si el sujeto de prueba toma un CoroutineDispatcher como dependencia, pasa una instancia de TestCoroutineDispatcher de la biblioteca de kotlinx-coroutines-test y ejecuta el cuerpo de la prueba dentro del método runBlockingTest de ese despachador:

private val coroutineDispatcher = TestCoroutineDispatcher()
private val uut = MyUnitUnderTest(coroutineDispatcher)

@Test
fun myTest() = coroutineDispatcher.runBlockingTest {
    // Test body
}