Kotlin Academy · บทเรียน

ทดสอบ StateFlow และ SharedFlow ด้วย Turbine

เขียนการทดสอบหน่วยสำหรับสถานะที่อาศัย Flow ด้วยไลบรารีทดสอบ Turbine

บทเรียน 4 จาก 414 ขั้นตอน

ทดสอบ StateFlow และ SharedFlow ด้วย Turbine เป็นบทเรียน Kotlin Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Kotlin Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Kotlin Academy มีบทเรียนทั้งหมด 4 บทเรียน

ทำไมต้องใช้ Turbine

Turbine เป็นไลบรารีสำหรับทดสอบ Kotlin Flow ที่ช่วยให้ตรวจสอบการปล่อยค่าได้ง่าย หากไม่มีไลบรารีนี้ การทดสอบโฟลว์จะต้องรวบรวมข้อมูลจากช่องด้วยตนเองพร้อมกำหนดเวลาหมด

// 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") }

การทดสอบโฟลว์เบื้องต้นด้วย test()

flow.test { } รวบรวมโฟลว์และช่วยให้คุณตรวจสอบการปล่อยค่าแต่ละค่าได้ด้วย awaitItem(), awaitComplete() และ 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()
    }
}

การทดสอบ StateFlow

StateFlow จะปล่อยค่าปัจจุบันของตนเองเสมอเมื่อเริ่มรวบรวมข้อมูล awaitItem() ของ Turbine จะรับค่าที่ปล่อยออกมาในครั้งแรกนั้น

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()
    }
}

การทดสอบเหตุการณ์ของ SharedFlow

SharedFlow ที่มี replay=0 จะปล่อยเฉพาะเหตุการณ์ในอนาคต ให้เริ่มรวบรวมข้อมูลก่อน แล้วจึงปล่อยค่า

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 กับเวลาเสมือน

runTest ใช้เวลาเสมือน การเรียก delay() จะเลื่อนเวลาเสมือนไปข้างหน้าในทันที ทำให้การทดสอบที่มีการหน่วงเวลาทำงานได้รวดเร็ว

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 สำหรับการทดสอบข้อยกเว้น

awaitError() ตรวจสอบว่าโฟลว์จบลงพร้อมข้อยกเว้น ให้ใช้ร่วมกับชนิดข้อยกเว้นที่คาดไว้

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 และ cancelAndIgnoreRemainingEvents

expectNoEvents() ตรวจสอบว่าไม่มีการปล่อยค่าใด ๆ cancelAndIgnoreRemainingEvents() ยกเลิกการรวบรวมข้อมูลโดยไม่สนใจการปล่อยค่าที่ค้างอยู่

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()
    }
}

การทดสอบ ViewModel ด้วย Turbine

ทดสอบ StateFlow ของ ViewModel ด้วยการใส่คลังข้อมูลจำลองเข้าไป แล้วตรวจสอบการเปลี่ยนแปลงของสถานะ

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()
    }
}

ตัวเก็บข้อมูลหลายตัวด้วย turbineScope

turbineScope { } ช่วยให้คุณทดสอบหลายโฟลว์พร้อมกัน โดยแต่ละโฟลว์มีอินสแตนซ์ 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()
    }
}

การทดสอบตัวดำเนินการของโฟลว์

ใช้ 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()
        }
}

การทดสอบด้วยการเลื่อนเวลาเทียม

ใช้ testScheduler.advanceTimeBy() เพื่อควบคุมเวลาเสมือนอย่างแม่นยำในการทดสอบที่เกี่ยวข้องกับ 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()
    }
}

ข้อผิดพลาดที่พบบ่อย

ยกเลิก Turbine เสมอเมื่อสิ้นสุดการทดสอบโฟลว์ร้อน หากลืมใช้ cancelAndIgnoreRemainingEvents() การทดสอบจะค้างระหว่างรอเหตุการณ์เพิ่มเติม

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()
    }
}

ตรวจสอบอย่างรวดเร็ว

ฟังก์ชัน Turbine ใดที่รวบรวมรายการที่ถูกปล่อยออกมาถัดไป และแสดงข้อผิดพลาดหากโฟลว์เสร็จสิ้นหรือเกิดข้อผิดพลาดก่อน

ทบทวน

Turbine ช่วยให้การทดสอบ Flow ง่ายขึ้นด้วย awaitItem(), awaitComplete() และ awaitError() ใช้ runTest สำหรับเวลาเสมือน และยกเลิกการทดสอบโฟลว์ร้อนด้วย cancelAndIgnoreRemainingEvents() เสมอ

เริ่มต้นได้ฟรี

เรียนรู้ Kotlin ด้วย AI tutor — ฟรี

เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป

คอร์ส
51
บทเรียน
203

คำถามที่พบบ่อย

บทเรียน “ทดสอบ StateFlow และ SharedFlow ด้วย Turbine” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “ทดสอบ StateFlow และ SharedFlow ด้วย Turbine” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Kotlin Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Kotlin Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “ทดสอบ StateFlow และ SharedFlow ด้วย Turbine”

เขียนการทดสอบหน่วยสำหรับสถานะที่อาศัย Flow ด้วยไลบรารีทดสอบ Turbine คุณปฏิบัติ Kotlin Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Kotlin Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Kotlin Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน

บทเรียน “ทดสอบ StateFlow และ SharedFlow ด้วย Turbine” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Kotlin Academy นี้ได้ไหม

ได้ บทเรียน Kotlin Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. StateFlow: ตัวเก็บสถานะแบบร้อนสำหรับ UI
  2. SharedFlow: บัสเหตุการณ์และเหตุการณ์แบบครั้งเดียว
  3. แปลง Cold Flow เป็น Hot ด้วย shareIn และ stateIn
  4. ทดสอบ StateFlow และ SharedFlow ด้วย Turbine
← กลับไปที่ Kotlin Academy