Testowanie StateFlow i SharedFlow z Turbine
Pisz testy jednostkowe stanu opartego na Flow, korzystając z biblioteki testowej Turbine.
Testowanie StateFlow i SharedFlow z Turbine to bezpłatna lekcja Kotlin Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Kotlin Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Kotlin Academy zawiera 4 lekcji w sumie.
Dlaczego Turbine?
Turbine to biblioteka testowa dla Kotlin Flow, która ułatwia sprawdzanie emisji za pomocą asercji. Bez niej testowanie przepływów wymaga ręcznego zbierania z kanału z limitami czasu.
// Add to test dependencies:
// testImplementation("app.cash.turbine:turbine:1.0.0")
// testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
import app.cash.turbine.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.test.*
fun main() { println("Turbine + runTest = clean flow testing") }Podstawy testowania Flow za pomocą test()
flow.test { } zbiera przepływ i pozwala sprawdzać każdą emisję za pomocą awaitItem(), awaitComplete() oraz awaitError().
import app.cash.turbine.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.test.*
fun testBasicFlow() = runTest {
val flow = flowOf(1, 2, 3)
flow.test {
assertEquals(1, awaitItem())
assertEquals(2, awaitItem())
assertEquals(3, awaitItem())
awaitComplete()
}
}Testowanie StateFlow
StateFlow zawsze emituje bieżącą wartość podczas rozpoczęcia zbierania. Funkcja awaitItem() biblioteki Turbine przechwytuje tę początkową emisję.
import app.cash.turbine.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.test.*
fun testStateFlow() = runTest {
val state = MutableStateFlow("loading")
state.test {
assertEquals("loading", awaitItem()) // initial value
state.value = "success"
assertEquals("success", awaitItem())
cancelAndIgnoreRemainingEvents()
}
}Testowanie zdarzeń SharedFlow
SharedFlow z replay=0 emituje tylko przyszłe zdarzenia. Najpierw rozpocznij zbieranie, a dopiero potem emituj.
import app.cash.turbine.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.test.*
fun testSharedFlow() = runTest {
val events = MutableSharedFlow<String>()
events.test {
events.emit("LoginSuccess")
assertEquals("LoginSuccess", awaitItem())
events.emit("Navigate")
assertEquals("Navigate", awaitItem())
cancelAndIgnoreRemainingEvents()
}
}runTest dla czasu wirtualnego
runTest używa czasu wirtualnego — wywołania delay() natychmiast przesuwają czas wirtualny, dzięki czemu testy zawierające opóźnienia są szybkie.
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.test.*
import app.cash.turbine.*
fun testDelayedFlow() = runTest {
val flow = flow {
delay(1000) // virtual — runs instantly
emit("done")
}
flow.test {
assertEquals("done", awaitItem())
awaitComplete()
}
}awaitError do testowania wyjątków
awaitError() sprawdza asercją, że przepływ kończy się wyjątkiem. Użyj go wraz z oczekiwanym typem wyjątku.
import app.cash.turbine.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.test.*
fun testFlowError() = runTest {
val flow = flow {
emit(1)
throw RuntimeException("stream error")
}
flow.test {
assertEquals(1, awaitItem())
val error = awaitError()
assertEquals("stream error", error.message)
}
}expectNoEvents i cancelAndIgnoreRemainingEvents
expectNoEvents() sprawdza asercją, że nic nie zostało wyemitowane. cancelAndIgnoreRemainingEvents() anuluje zbieranie, ignorując oczekujące emisje.
import app.cash.turbine.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.test.*
fun testNoEvents() = runTest {
val flow = MutableSharedFlow<Int>()
flow.test {
expectNoEvents() // nothing emitted yet
flow.emit(1)
assertEquals(1, awaitItem())
cancelAndIgnoreRemainingEvents()
}
}Testowanie ViewModelu za pomocą Turbine
Testuj StateFlow ViewModelu, wstrzykując fałszywe repozytorium i sprawdzając przejścia stanu.
import app.cash.turbine.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.test.*
sealed class State { object Loading : State(); data class Data(val v: String) : State() }
class FakeRepo { suspend fun fetch() = "result" }
class VM(repo: FakeRepo, scope: kotlinx.coroutines.CoroutineScope) {
private val _s = MutableStateFlow<State>(State.Loading)
val state = _s.asStateFlow()
init { scope.launch { _s.value = State.Data(repo.fetch()) } }
}
fun testVM() = runTest {
val vm = VM(FakeRepo(), backgroundScope)
vm.state.test {
assertEquals(State.Loading, awaitItem())
val data = awaitItem() as State.Data
assertEquals("result", data.v)
cancelAndIgnoreRemainingEvents()
}
}Wielu odbiorców za pomocą turbineScope
turbineScope { } pozwala jednocześnie testować wiele przepływów, z których każdy ma własną instancję Turbine.
import app.cash.turbine.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.test.*
fun testMultipleFlows() = runTest {
val a = MutableStateFlow(1)
val b = MutableStateFlow("x")
turbineScope {
val aTurbine = a.testIn(backgroundScope)
val bTurbine = b.testIn(backgroundScope)
assertEquals(1, aTurbine.awaitItem())
assertEquals("x", bTurbine.awaitItem())
a.value = 2
assertEquals(2, aTurbine.awaitItem())
aTurbine.cancelAndIgnoreRemainingEvents()
bTurbine.cancelAndIgnoreRemainingEvents()
}
}Testowanie operatorów Flow
Testuj w izolacji działanie własnych operatorów przepływu i transformacji za pomocą Turbine.
import app.cash.turbine.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.test.*
fun testOperators() = runTest {
(1..5).asFlow()
.filter { it % 2 == 0 }
.map { it * 10 }
.test {
assertEquals(20, awaitItem())
assertEquals(40, awaitItem())
awaitComplete()
}
}Testowanie z ręcznym przesuwaniem czasu
Użyj testScheduler.advanceTimeBy(), aby precyzyjnie kontrolować czas wirtualny w testach obejmujących delay.
import app.cash.turbine.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.test.*
fun testTimedFlow() = runTest {
val flow = flow {
emit("start")
delay(500)
emit("end")
}
flow.test {
assertEquals("start", awaitItem())
testScheduler.advanceTimeBy(500)
assertEquals("end", awaitItem())
awaitComplete()
}
}Częste błędy
Zawsze anuluj Turbine na końcu testów gorących przepływów. Pominięcie cancelAndIgnoreRemainingEvents() powoduje zawieszenie testów podczas oczekiwania na kolejne zdarzenia.
import app.cash.turbine.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.test.*
fun testGoodPractice() = runTest {
val state = MutableStateFlow(0)
state.test {
awaitItem() // initial 0
state.value = 1
awaitItem() // 1
// Always cancel hot flows:
cancelAndIgnoreRemainingEvents()
}
}Szybkie sprawdzenie
Która funkcja Turbine zbiera następny wyemitowany element i zgłasza wyjątek, jeśli przepływ najpierw się zakończy lub zakończy błędem?
Podsumowanie
Turbine upraszcza testowanie Flow za pomocą awaitItem(), awaitComplete() i awaitError(). Używaj runTest do pracy z czasem wirtualnym. Zawsze anuluj testy gorących przepływów za pomocą cancelAndIgnoreRemainingEvents().
Często zadawane pytania
Czy lekcja „Testowanie StateFlow i SharedFlow z Turbine” jest bezpłatna?
Tak — pełny tekst „Testowanie StateFlow i SharedFlow z Turbine” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Kotlin Academy, przejdź na CoddyKit PRO. Kurs Kotlin Academy zawiera 4 lekcji w sumie.
Co nauczysz się w „Testowanie StateFlow i SharedFlow z Turbine”?
Pisz testy jednostkowe stanu opartego na Flow, korzystając z biblioteki testowej Turbine. Ćwiczysz Kotlin Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć Kotlin Academy?
Nie wymagamy żadnego doświadczenia. Kotlin Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.
Ile czasu zajmuje lekcja „Testowanie StateFlow i SharedFlow z Turbine”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji Kotlin Academy?
Tak. Każda lekcja Kotlin Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- StateFlow: gorący kontener stanu dla interfejsu
- SharedFlow: magistrale zdarzeń i zdarzenia jednorazowe
- Konwersja zimnego Flow na gorący za pomocą shareIn i stateIn
- Testowanie StateFlow i SharedFlow z Turbine