Проверка последовательности эмиссий Flow библиотекой тестирования Turbine
SearchRepository.search(query) возвращает Flow<UiState>, который обязан выдать ровно UiState.Loading, затем UiState.Done и после этого завершиться — больше ничего.
Допишите тело теста так, чтобы оно проверяло именно этот порядок И доказывало, что после Done не пришло ни одного лишнего элемента. Используйте библиотеку тестирования Flow Turbine; тест уже запущен через runTest.
@Test
fun `search emits Loading then Done`() = runTest {
val flow: Flow<UiState> = repository.search(query = "kotlin")
// ваш код здесь
}
Допишите реализацию.
Соберите поток внутри блока test { } из Turbine и вытягивайте элементы по порядку: flow.test { assertEquals(UiState.Loading, awaitItem()); assertEquals(UiState.Done, awaitItem()); awaitComplete() }. Именно awaitComplete() доказывает, что лишних эмиссий не было.
- ✗Проверять только элементы и не закрывать блок, из-за чего лишняя эмиссия после
Doneпроходит незамеченной - ✗Хвататься за
first()илиtoList()на бесконечном потоке, из-за чего тест виснет, а не падает - ✗Считать
awaitItem()подглядыванием в последнее значение, а не вытягиванием следующей эмиссии по порядку
- →Как изменится тест, если поток никогда не завершается, как это делает
StateFlow? - →Что сообщит библиотека тестирования Flow
Turbine, если поток выдаст на один элемент больше, чем вы ждали?
Решение
Turbine даёт блок test { }, внутри которого поток собирается, а элементы вытягиваются по одному через awaitItem(). Порядок проверяется самой последовательностью вызовов.
@Test
fun `search emits Loading then Done`() = runTest {
val flow: Flow<UiState> = repository.search(query = "kotlin")
flow.test {
assertEquals(UiState.Loading, awaitItem())
assertEquals(UiState.Done, awaitItem())
awaitComplete() // ✅ поток завершился, лишних эмиссий не было
}
}
Ключевая деталь — последняя строка. awaitComplete() требует, чтобы поток завершился ровно здесь: если после Done придёт ещё один элемент, Turbine уронит тест с сообщением о неожиданной эмиссии. Без этой строки лишний элемент остался бы незамеченным.
Для потока, который никогда не завершается (например, StateFlow), вместо awaitComplete() берут cancelAndIgnoreRemainingEvents() — он отменяет сбор и сообщает, что дальше проверять нечего.