Turbine لـ Kotlin: دليل شامل لاختبار Flow
1. ما هي Turbine ولماذا تحتاجها؟
مكتبة Turbine هي أداة حديثة لاختبار تدفقات البيانات غير المتزامنة (Flow) في لغة Kotlin. طورتها شركة Cash App، وتحل Turbine واحدة من أكثر المشاكل إيلامًا عند كتابة اختبارات الوحدة للكود غير المتزامن: صعوبة التحقق من القيم المنبعثة من Flow، ومعالجة الأخطاء، وإنهاء التدفق. على عكس الطرق التقليدية مثل استخدام kotlinx-coroutines-test مع جمع العناصر يدويًا، توفر Turbine واجهة برمجة تطبيقات (API) تصريحية وبديهية تجعل الاختبارات قابلة للقراءة وموثوقة.
لماذا تحتاج Turbine؟ تخيل أنك تكتب ViewModel تشترك في مستودع (Repository) وتحول البيانات إلى حالة واجهة المستخدم. بدون Turbine، سيتعين عليك إنشاء مجمعات (Collectors) يدويًا، وإدارة المهام (Job)، ومزامنة الكوروتينات. تقوم Turbine بأتمتة هذه العملية: فهي تنشئ كوروتينًا افتراضيًا، وتجمع جميع الانبعاثات في "توربين" (ومن هنا جاء الاسم)، ثم تتيح لك التحقق من كل قيمة بالترتيب، متجاهلة الحالات الوسيطة غير المهمة. هذا مفيد بشكل خاص عند اختبار التدفقات المشابهة لـ BehaviourSubject، حيث تكون الحالة النهائية فقط هي المهمة.
تشمل الإمكانيات الرئيسية للمكتبة: التحقق من القيم المنبعثة بترتيب صارم، وتجاهل القيم الوسيطة باستخدام skip()، واختبار الأخطاء عبر awaitError()، والتحقق من إنهاء التدفق عبر awaitComplete()، بالإضافة إلى العمل مع القنوات (Channel) و StateFlow. تتكامل Turbine مع أي إطار عمل للاختبار (JUnit، Kotest، Spek) ولا تتطلب إعدادات إضافية — فقط أضف التبعية واستخدم testScope.
2. التثبيت
لاستخدام Turbine في مشروعك، أضف التبعية التالية إلى ملف build.gradle.kts (أو build.gradle):
// build.gradle.kts
dependencies {
// لمشاريع JVM
testImplementation("app.cash.turbine:turbine:1.1.0")
// إذا كنت تستخدم kotlinx-coroutines-test (موصى به)
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0")
}
// لمشاريع Android (داخل كتلة android)
android {
testOptions {
unitTests.isReturnDefaultValues = true
}
}
إذا كنت تستخدم Maven:
<dependency>
<groupId>app.cash.turbine</groupId>
<artifactId>turbine</artifactId>
<version>1.1.0</version>
<scope>test</scope>
</dependency>
الحد الأدنى من المتطلبات: Kotlin 1.9+، kotlinx-coroutines 1.8+. تدعم المكتبة JVM وAndroid وJS وNative (عبر Kotlin Multiplatform).
3. البداية السريعة
لنلقِ نظرة على مثال بسيط: اختبار Flow يصدر ثلاثة أرقام.
import app.cash.turbine.test
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.junit.Test
class SimpleTurbineTest {
@Test
fun `test flow emits three values`() = runTest {
val flow = flowOf(1, 2, 3)
flow.test {
// awaitItem() تنتظر الانبعاث التالي وتعيده
assertEquals(1, awaitItem())
assertEquals(2, awaitItem())
assertEquals(3, awaitItem())
// awaitComplete() تتحقق من أن التدفق انتهى بدون أخطاء
awaitComplete()
}
}
}
شغّل هذا الاختبار — سينجح. لاحظ أن test هي دالة معلقة (suspend function) تبدأ جمع التدفق في كوروتين منفصل. جميع استدعاءات awaitItem() تمنع الكوروتين الحالي حتى ظهور العنصر التالي. إذا انتهى التدفق قبل أن نتوقع، سيفشل الاختبار برسالة خطأ واضحة.