0Pricing
Kotlin Academy · Lekcja

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

  1. StateFlow: gorący kontener stanu dla interfejsu
  2. SharedFlow: magistrale zdarzeń i zdarzenia jednorazowe
  3. Konwersja zimnego Flow na gorący za pomocą shareIn i stateIn
  4. Testowanie StateFlow i SharedFlow z Turbine
← Powrót do Kotlin Academy