اختبار StateFlow وSharedFlow باستخدام Turbine
اكتب اختبارات وحدة للحالة المعتمدة على Flow باستخدام مكتبة اختبار Turbine.
اختبار StateFlow وSharedFlow باستخدام Turbine درس مجاني في Kotlin Academy على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 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().
الأسئلة الشائعة
هل درس «اختبار StateFlow وSharedFlow باستخدام Turbine» مجاني؟
نعم — نص درس «اختبار StateFlow وSharedFlow باستخدام Turbine» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Kotlin Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Kotlin Academy 4 دروس في المجموع.
ماذا ستتعلم في «اختبار StateFlow وSharedFlow باستخدام Turbine»؟
اكتب اختبارات وحدة للحالة المعتمدة على Flow باستخدام مكتبة اختبار Turbine. تتمرن على Kotlin Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Kotlin Academy؟
لا تُشترط خبرة سابقة. Kotlin Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.
كم من الوقت يستغرق درس «اختبار StateFlow وSharedFlow باستخدام Turbine»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Kotlin Academy هذا؟
نعم. كل درس في Kotlin Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- StateFlow: حاوية حالة ساخنة لواجهة المستخدم
- SharedFlow: حافلات الأحداث والأحداث أحادية الإرسال
- تحويل Flow البارد إلى ساخن باستخدام shareIn وstateIn
- اختبار StateFlow وSharedFlow باستخدام Turbine