Mockito هو إطار عمل مفتوح المصدر لإنشاء كائنات mock في اختبارات الوحدة بلغة Java و Kotlin، والذي يسمح بعزل الكود المختبر عن التبعيات الخارجية. بمساعدته، يقوم المطور باستبدال المستودعات الحقيقية وعملاء API وقواعد البيانات بـ stubs خاضعة للتحكم بسلوك محدد مسبقاً. وفقاً Mockito.org، تُستخدم المكتبة في أكثر من 60% من مشاريع Java التي تستخدم اختبارات الوحدة.
النقاط الرئيسية
Mockito هي مكتبة مفتوحة المصدر لإنشاء كائنات mock (stubs) في اختبارات الوحدة بلغة Java و Kotlin ولغات JVM الأخرى. على عكس JUnit المسؤولة عن تشغيل الاختبارات، يحل Mockito مشكلة العزل — يستبدل التبعيات الحقيقية للفئة المختبرة بكائنات قابلة للتنبؤ.
بدون mocks، اختبار طريقة تصل إلى قاعدة بيانات أو API خارجي يتطلب إعداد بيئة حقيقية — نشر قاعدة بيانات، تشغيل خادم. يستبدل Mockito هذه التبعيات بكائنات ذات سلوك ثابت: طريقة repository.findById(1) تعيد دائماً كائن User محدد دون الوصول إلى قاعدة البيانات.
تعتمد بنية Mockito على نمط Proxy (للوصولات والفئات). تقوم المكتبة بإنشاء فئة فرعية أو وكيل للنوع المحدد وتعترض جميع استدعاءات الطرق، مع إرجاع قيم افتراضية أو قيم محددة عبر when().thenReturn().
يعتمد مبدأ عمل Mockito على ثلاث عمليات أساسية: إنشاء mock، تكوين السلوك (stubbing) والتحقق من الاستدعاءات (verification). تستخدم كل عملية طرقاً ثابتة من الفئة org.mockito.Mockito — الفئة الأكثر تحميلاً في نظام Java البيئي حسب إحصائيات Maven Central. يتم تسجيل جميع استدعاءات طرق mock في الذاكرة، مما يسمح بالتحقق منها لاحقاً عبر verify.
يتكون الاختبار النموذجي مع Mockito من ثلاث مراحل: Arrange — إنشاء mocks وتكوين stubs عبر when().thenReturn()، Act — استدعاء الطريقة المختبرة، Assert — التحقق من النتيجة عبر assertEquals و verify(mock). يُسمى هذا النهج AAA (Arrange-Act-Assert).
لننظر إلى اختبار بسيط حيث يستبدل Mockito مستودع المستخدمين. تقوم طريقة when().thenReturn() بتكوين mock بحيث يعيد استدعاء findById كائن User مُعد مسبقاً.
// إنشاء mock للمستودع
UserRepository mockRepo = mock(UserRepository.class);
// تكوين السلوك: findById(1) يعيد مستخدماً
when(mockRepo.findById(1)).thenReturn(new User("Alice"));
// التحقق من أن الطريقة تم استدعاؤها بالفعل
User result = mockRepo.findById(1);
assertEquals("Alice", result.getName());
verify(mockRepo).findById(1);
Mockito يوفر طريقتين لإنشاء mocks: الطريقة الثابتة mock(Class) والتعليق التوضيحي @Mock مع التهيئة عبر MockitoAnnotations.openMocks(). الطريقة الأولى مدمجة لواحد أو اثنين من mocks، والثانية مناسبة عندما يكون هناك العديد من التبعيات — تقلل التعليقات التوضيحية من كود boilerplate.
تستقبل طريقة mock() فئة وتعيد كائن stub يمكن تكوينه عبر when().thenReturn(). جميع الطرق غير المهيأة تعيد قيماً افتراضية: 0 للأرقام، false للقيم المنطقية، null للكائنات.
ApiClient apiClient = mock(ApiClient.class);
Database database = mock(Database.class);
التعليق التوضيحي @Mock مع @ExtendWith(MockitoExtension.class) ينشئ تلقائياً mocks لجميع حقول فئة الاختبار. يتولى MockitoExtension مسؤولية التهيئة قبل كل اختبار.
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void getUserShouldReturnUserFromRepo() {
when(userRepository.findById(1)).thenReturn(new User("Alice"));
User result = userService.getUser(1);
assertEquals("Alice", result.getName());
}
}
Stubbing هو عملية تحديد ما يجب أن تعيده طريقة mock عند استدعائها بوسائط محددة. الصيغة الأساسية: when(mock.method(args)).thenReturn(value). لسيناريوهات مختلفة، يقدم Mockito عدة متغيرات لطرق then.
| الطريقة | الغرض |
|---|---|
| thenReturn(value) | تعيد دائماً القيمة المحددة |
| thenThrow(exception) | تلقي استثناء عند الاستدعاء |
| thenAnswer(answer) | تحسب القيمة المعادة ديناميكياً |
| thenCallRealMethod() | تستدعي الطريقة الحقيقية (mock جزئي) |
عندما تعتمد القيمة المعادة على وسائط الاستدعاء، يُستخدم thenAnswer مع lambda. هذا مفيد لمحاكاة العمل مع بيانات حقيقية — على سبيل المثال، توليد ID بناءً على الكائن المستلم.
when(repository.save(any())).thenAnswer(invocation -> {
User user = invocation.getArgument(0);
user.setId(42);
return user;
});
Verify هي ميزة فريدة في Mockito لا توفرها مكتبات كائنات mock الأقدم (EasyMock، jMock).
Verify يجعل الاختبارات أكثر موثوقية لأنه يتحقق ليس فقط من القيمة المعادة ولكن أيضاً من الآثار الجانبية — استدعاءات الطرق التي لا تعيد نتيجة (طرق void). طريقة verify(mock).methodName(args) تتحقق مما إذا تم استدعاء طريقة محددة من mock بالوسائط المحددة. هذا يسمح باختبار ليس فقط النتيجة ولكن أيضاً العملية — حقيقة الوصول إلى التبعية.
افتراضياً، يتحقق verify من أن الطريقة تم استدعاؤها مرة واحدة بالضبط. إذا كان عدد مختلف مطلوباً، يُستخدم times(n) و atLeast(n) و never() ومعدلات أخرى من الفئة Mockito.
// التحقق من عدد الاستدعاءات
verify(repository, times(3)).save(any());
verify(repository, never()).delete(any());
verify(repository, atLeastOnce()).findById(1);
// التحقق من ترتيب الاستدعاءات
InOrder inOrder = inOrder(repository);
inOrder.verify(repository).save(any());
inOrder.verify(repository).flush();
عندما تحتاج للتحقق من الكائن الذي تم استدعاء الطريقة به بالضبط، يُستخدم ArgumentCaptor. يلتقط قيمة الوسيطة أثناء الاستدعاء ويسمح بالتحقق من حقولها بشكل فردي. ArgumentCaptor مفيد بشكل خاص عندما يقوم الكود المختبر بإنشاء كائن داخلياً ويمرره إلى تبعية — لا يمكنك التحقق من ذلك الكائن بطريقة أخرى.
ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
verify(repository).save(captor.capture());
assertEquals("Alice", captor.getValue().getName());
@Mock و @InjectMocks هما تعليقان توضيحيان رئيسيان في Mockito يقللان بشكل كبير من كود boilerplate. @Mock ينشئ mock لحقل، و @InjectMocks يحقن جميع mocks من فئة الاختبار في الكائن المختبر عبر المُنشئ أو setter أو الحقل.
آلية @InjectMocks تحاول حقن التبعيات بالترتيب التالي: المُنشئ الذي يحتوي على أكبر عدد من الوسائط، setter حسب النوع، الحقل الخاص. إذا لم تعمل أي من هذه الطرق، يبقى الكائن مع تبعيات null، وسيفشل الاختبار مع NullPointerException.
من المهم أن نفهم: @InjectMocks لا يحلل أنواع الحقول — يستبدل أي mock متوافق حسب النوع. إذا كانت الفئة تحتوي على حقلين من نفس النوع، قد يحقن Mockito mock الخطأ. في هذه الحالات، يُوصى باستخدام مُنشئ صريح مع معاملات mock.
في تطوير Android، يُستخدم Mockito مع JUnit لاختبار ViewModel و Repository و UseCase. نظراً لأن هذه الفئات تعمل على JVM دون سياق Android، يستبدل Mockito تبعياتها — Room DAO و Retrofit API و SharedPreferences — بـ stubs بسلوك قابل للتنبؤ.
لإضافة Mockito إلى مشروع Android، يكفي إضافة التبعية mockito-core أو mockito-inline (الأخيرة تدعم mocking للفئات النهائية والطرق الثابتة). الإصدار 5.12.0 (2024) يتضمن دعم Java 21 وتحسين التكامل مع JUnit 5.
// build.gradle.kts (module)
dependencies {
testImplementation("org.mockito:mockito-core:5.12.0")
testImplementation("org.mockito:mockito-junit-jupiter:5.12.0")
}
سابقاً، كان mocking الطرق الثابتة والمُنشئات يتطلب PowerMock — إضافة تعمل عبر إعادة تشكيل bytecode. بدءاً من Mockito 5.x مع mockito-inline، هذه الإمكانية مدمجة مباشرة: mockStatic(ClassName.class) يسمح بـ mocking الطرق الثابتة دون مكتبات إضافية.
سيناريو نموذجي: ViewModel يستدعي طريقة من المستودع ويحول النتيجة إلى حالة واجهة المستخدم. Mockito يستبدل المستودع، ويختبر الاختبار أن ViewModel يعالج بشكل صحيح كلاً من الاستجابة الناجحة والخطأ. عند استخدام Clean Architecture، يتم إنشاء mocks لكل طبقة: DataSource و Repository و UseCase — وهذا يسمح باختبار كل طبقة بشكل معزول.
الأسئلة الشائعة
Mockito هي مكتبة لـ Java و Kotlin تستخدم الوكلاء والانعكاس. MockK هي مكتبة Kotlin-first مع دعم للروتينات المساعدة ووظائف التوسيع والفئات النهائية دون تكوين إضافي.
بدءاً من Mockito 2.1، يتم دعم mocking الفئات النهائية عبر opt-in. في الإصدار 5.x (mockito-inline)، هذا مفعل افتراضياً. يكفي إضافة التبعية mockito-inline واستخدام طريقة mock() القياسية.
Spy هو mock جزئي يقوم افتراضياً باستدعاء الطرق الحقيقية ولكنه يسمح بتجاوز بعضها عبر when().thenReturn(). Spy مفيد لاختبار الكود القديم عندما لا يمكن إعادة كتابة الفئة بأكملها.
thenReturn يعيد دائماً نفس القيمة بغض النظر عن الوسائط. thenAnswer يحسب القيمة المعادة بناءً على الاستدعاء — وسائط الاستدعاء و mock نفسه والحالة. للاستجابات الديناميكية، استخدم دائماً thenAnswer.
Verify يتحقق ليس فقط من النتيجة ولكن أيضاً من العملية — حقيقة الوصول إلى التبعية. هذا أمر بالغ الأهمية للخدمات التي يجب أن تحفظ البيانات أو ترسل الإشعارات. بدون verify، لن يكتشف الاختبار أن طريقة معينة لم تستدع save() أو send().
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا