مقدمة عن Mockk: ما هي ولماذا تحتاجها
Mockk هي مكتبة حديثة لإنشاء المحاكاة (mock objects) في لغة البرمجة Kotlin. صُممت خصيصًا لتراعي خصائص Kotlin الفريدة مثل: القابلية للصفر (nullability)، دوال التوسيع (extension functions)، coroutines، الكائنات المرافقة (companion objects)، وغيرها من الميزات غير الموجودة في Java.
لماذا تحتاج مكتبة Mockk؟ عند كتابة اختبارات الوحدة (unit tests)، غالبًا ما نواجه موقفًا تعتمد فيه الفئة المختبرة على فئات أخرى، خدمات، قواعد بيانات، أو واجهات برمجية خارجية (APIs). لعزل الكود المختبر وجعل الاختبارات سريعة وموثوقة ومستقلة عن العوامل الخارجية، نقوم بإنشاء "mock objects" — وهي كائنات وهمية تحاكي سلوك التبعيات الحقيقية.
أهم إمكانيات Mockk:
- إنشاء mock للفئات، الواجهات، والكائنات
- دعم التعليقات التوضيحية (@MockK, @RelaxedMockK, @SpyK)
- العمل مع coroutines (coEvery, coVerify)
- محاكاة دوال التوسيع (extension functions)
- محاكاة الطرق الثابتة والكائنات المرافقة
- دعم الفتحات (slot) لالتقاط الوسائط
- دعم المحاكاة الجزئية (spy)
تثبيت Mockk
يتم توزيع Mockk عبر Maven Central. لإضافتها إلى مشروعك، أضف التبعية في ملف build.gradle.kts (لـ Gradle مع Kotlin DSL):
// build.gradle.kts
dependencies {
testImplementation("io.mockk:mockk:1.13.10")
}
لـ Gradle مع Groovy DSL:
// build.gradle
dependencies {
testImplementation "io.mockk:mockk:1.13.10"
}
لـ Maven:
<dependency>
<groupId>io.mockk</groupId>
<artifactId>mockk</artifactId>
<version>1.13.10</version>
<scope>test</scope>
</dependency>
إذا كنت تستخدم coroutines، لا حاجة لإضافة أي تبعيات إضافية — دعم coroutines مدمج في Mockk.
بداية سريعة مع مثال عملي
لنأخذ مثالًا بسيطًا. لنفترض أن لدينا خدمة UserService تعتمد على مستودع UserRepository:
// الفئات المراد اختبارها
class UserRepository {
fun findById(id: Long): String {
// التنفيذ الحقيقي يتصل بقاعدة البيانات
return "مستخدم حقيقي"
}
}
class UserService(private val repository: UserRepository) {
fun getUserName(id: Long): String {
val user = repository.findById(id)
return if (user.isNotEmpty()) user else "غير معروف"
}
}
الآن لنكتب اختبارًا باستخدام Mockk:
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import org.junit.jupiter.api.Test
class UserServiceTest {
@Test
fun `getUserName يجب أن يعيد اسم المستخدم من المستودع`() {
// 1. إنشاء mock للمستودع
val mockRepository = mockk<UserRepository>()
// 2. تحديد سلوك الـ mock
every { mockRepository.findById(1L) } returns "أحمد علي"
// 3. إنشاء الكائن المختبر مع الـ mock
val service = UserService(mockRepository)
// 4. استدعاء الطريقة المختبرة
val result = service.getUserName(1L)
// 5. التحقق من النتيجة
assert(result == "أحمد علي")
// 6. التحقق من أن الطريقة استُدعيت مرة واحدة بالضبط
verify(exactly = 1) { mockRepository.findById(1L) }
}
}
الطرق والدوال الأساسية في Mockk
سنستعرض 10 طرق رئيسية في المكتبة مع شرح مفصل وأمثلة.
1. mockk() — إنشاء كائن mock
الوظيفة: تنشئ كائن mock للفئة أو الواجهة المحددة. جميع طرق الـ mock تعيد قيمًا افتراضية (null، 0، false) افتراضيًا، وتلقي استثناءً إذا لم يتم تحديد السلوك.
المعاملات:
cls: KClass<T>