Mockito — ما هو، المفاهيم الأساسية ومبدأ العمل

المؤلف: IT Sectr نُشر: 2026-04-08 وقت القراءة: 8 دق

Mockito هو إطار عمل مفتوح المصدر لإنشاء كائنات mock في اختبارات الوحدة بلغة Java و Kotlin، والذي يسمح بعزل الكود المختبر عن التبعيات الخارجية. بمساعدته، يقوم المطور باستبدال المستودعات الحقيقية وعملاء API وقواعد البيانات بـ stubs خاضعة للتحكم بسلوك محدد مسبقاً. وفقاً Mockito.org، تُستخدم المكتبة في أكثر من 60% من مشاريع Java التي تستخدم اختبارات الوحدة.

النقاط الرئيسية

  • Mockito — مكتبة لإنشاء كائنات mock تحل محل التبعيات الحقيقية في الاختبارات.
  • Mock — كائن stub يحاكي سلوك المكون الحقيقي.
  • Stubbing — تكوين القيمة المعادة عند استدعاء طريقة mock.
  • Verify — التحقق من أن طريقة معينة تم استدعاؤها بوسائط محددة.
  • @InjectMocks — الحقن التلقائي لتبعيات mock في الكائن المختبر.

ما هو Mockito؟

Mockito هي مكتبة مفتوحة المصدر لإنشاء كائنات mock (stubs) في اختبارات الوحدة بلغة Java و Kotlin ولغات JVM الأخرى. على عكس JUnit المسؤولة عن تشغيل الاختبارات، يحل Mockito مشكلة العزل — يستبدل التبعيات الحقيقية للفئة المختبرة بكائنات قابلة للتنبؤ.

بدون mocks، اختبار طريقة تصل إلى قاعدة بيانات أو API خارجي يتطلب إعداد بيئة حقيقية — نشر قاعدة بيانات، تشغيل خادم. يستبدل Mockito هذه التبعيات بكائنات ذات سلوك ثابت: طريقة repository.findById(1) تعيد دائماً كائن User محدد دون الوصول إلى قاعدة البيانات.

تعتمد بنية Mockito على نمط Proxy (للوصولات والفئات). تقوم المكتبة بإنشاء فئة فرعية أو وكيل للنوع المحدد وتعترض جميع استدعاءات الطرق، مع إرجاع قيم افتراضية أو قيم محددة عبر when().thenReturn().

كيف يعمل Mockito

يعتمد مبدأ عمل Mockito على ثلاث عمليات أساسية: إنشاء mock، تكوين السلوك (stubbing) والتحقق من الاستدعاءات (verification). تستخدم كل عملية طرقاً ثابتة من الفئة org.mockito.Mockito — الفئة الأكثر تحميلاً في نظام Java البيئي حسب إحصائيات Maven Central. يتم تسجيل جميع استدعاءات طرق mock في الذاكرة، مما يسمح بالتحقق منها لاحقاً عبر verify.

ثلاث خطوات لاختبار مع Mockito

يتكون الاختبار النموذجي مع Mockito من ثلاث مراحل: Arrange — إنشاء mocks وتكوين stubs عبر when().thenReturn()، Act — استدعاء الطريقة المختبرة، Assert — التحقق من النتيجة عبر assertEquals و verify(mock). يُسمى هذا النهج AAA (Arrange-Act-Assert).

مثال أساسي مع mock للمستودع

لننظر إلى اختبار بسيط حيث يستبدل Mockito مستودع المستخدمين. تقوم طريقة when().thenReturn() بتكوين mock بحيث يعيد استدعاء findById كائن User مُعد مسبقاً.

java
// إنشاء 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);

إنشاء كائنات mock

Mockito يوفر طريقتين لإنشاء mocks: الطريقة الثابتة mock(Class) والتعليق التوضيحي @Mock مع التهيئة عبر MockitoAnnotations.openMocks(). الطريقة الأولى مدمجة لواحد أو اثنين من mocks، والثانية مناسبة عندما يكون هناك العديد من التبعيات — تقلل التعليقات التوضيحية من كود boilerplate.

عبر الطريقة الثابتة mock()

تستقبل طريقة mock() فئة وتعيد كائن stub يمكن تكوينه عبر when().thenReturn(). جميع الطرق غير المهيأة تعيد قيماً افتراضية: 0 للأرقام، false للقيم المنطقية، null للكائنات.

java
ApiClient apiClient = mock(ApiClient.class);
Database database = mock(Database.class);

عبر التعليق التوضيحي @Mock مع JUnit 5

التعليق التوضيحي @Mock مع @ExtendWith(MockitoExtension.class) ينشئ تلقائياً mocks لجميع حقول فئة الاختبار. يتولى MockitoExtension مسؤولية التهيئة قبل كل اختبار.

java
@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

Stubbing هو عملية تحديد ما يجب أن تعيده طريقة mock عند استدعائها بوسائط محددة. الصيغة الأساسية: when(mock.method(args)).thenReturn(value). لسيناريوهات مختلفة، يقدم Mockito عدة متغيرات لطرق then.

الطريقةالغرض
thenReturn(value)تعيد دائماً القيمة المحددة
thenThrow(exception)تلقي استثناء عند الاستدعاء
thenAnswer(answer)تحسب القيمة المعادة ديناميكياً
thenCallRealMethod()تستدعي الطريقة الحقيقية (mock جزئي)

استجابة ديناميكية عبر thenAnswer

عندما تعتمد القيمة المعادة على وسائط الاستدعاء، يُستخدم thenAnswer مع lambda. هذا مفيد لمحاكاة العمل مع بيانات حقيقية — على سبيل المثال، توليد ID بناءً على الكائن المستلم.

java
when(repository.save(any())).thenAnswer(invocation -> {
    User user = invocation.getArgument(0);
    user.setId(42);
    return user;
});

Verify: التحقق من التفاعلات مع mock

Verify هي ميزة فريدة في Mockito لا توفرها مكتبات كائنات mock الأقدم (EasyMock، jMock).

Verify يجعل الاختبارات أكثر موثوقية لأنه يتحقق ليس فقط من القيمة المعادة ولكن أيضاً من الآثار الجانبية — استدعاءات الطرق التي لا تعيد نتيجة (طرق void). طريقة verify(mock).methodName(args) تتحقق مما إذا تم استدعاء طريقة محددة من mock بالوسائط المحددة. هذا يسمح باختبار ليس فقط النتيجة ولكن أيضاً العملية — حقيقة الوصول إلى التبعية.

التحقق من عدد الاستدعاءات

افتراضياً، يتحقق verify من أن الطريقة تم استدعاؤها مرة واحدة بالضبط. إذا كان عدد مختلف مطلوباً، يُستخدم times(n) و atLeast(n) و never() ومعدلات أخرى من الفئة Mockito.

java
// التحقق من عدد الاستدعاءات
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 مفيد بشكل خاص عندما يقوم الكود المختبر بإنشاء كائن داخلياً ويمرره إلى تبعية — لا يمكنك التحقق من ذلك الكائن بطريقة أخرى.

java
ArgumentCaptor<User> captor = ArgumentCaptor.forClass(User.class);
verify(repository).save(captor.capture());
assertEquals("Alice", captor.getValue().getName());

التعليقات التوضيحية @Mock و @InjectMocks

@Mock و @InjectMocks هما تعليقان توضيحيان رئيسيان في Mockito يقللان بشكل كبير من كود boilerplate. @Mock ينشئ mock لحقل، و @InjectMocks يحقن جميع mocks من فئة الاختبار في الكائن المختبر عبر المُنشئ أو setter أو الحقل.

آلية @InjectMocks تحاول حقن التبعيات بالترتيب التالي: المُنشئ الذي يحتوي على أكبر عدد من الوسائط، setter حسب النوع، الحقل الخاص. إذا لم تعمل أي من هذه الطرق، يبقى الكائن مع تبعيات null، وسيفشل الاختبار مع NullPointerException.

قواعد استخدام @InjectMocks

من المهم أن نفهم: @InjectMocks لا يحلل أنواع الحقول — يستبدل أي mock متوافق حسب النوع. إذا كانت الفئة تحتوي على حقلين من نفس النوع، قد يحقن Mockito mock الخطأ. في هذه الحالات، يُوصى باستخدام مُنشئ صريح مع معاملات mock.

Mockito في مشاريع Android

في تطوير Android، يُستخدم Mockito مع JUnit لاختبار ViewModel و Repository و UseCase. نظراً لأن هذه الفئات تعمل على JVM دون سياق Android، يستبدل Mockito تبعياتها — Room DAO و Retrofit API و SharedPreferences — بـ stubs بسلوك قابل للتنبؤ.

إعداد Gradle لـ Mockito

لإضافة Mockito إلى مشروع Android، يكفي إضافة التبعية mockito-core أو mockito-inline (الأخيرة تدعم mocking للفئات النهائية والطرق الثابتة). الإصدار 5.12.0 (2024) يتضمن دعم Java 21 وتحسين التكامل مع JUnit 5.

kotlin
// build.gradle.kts (module)
dependencies {
    testImplementation("org.mockito:mockito-core:5.12.0")
    testImplementation("org.mockito:mockito-junit-jupiter:5.12.0")
}

Mockito و PowerMock: ممارسة قديمة

سابقاً، كان mocking الطرق الثابتة والمُنشئات يتطلب PowerMock — إضافة تعمل عبر إعادة تشكيل bytecode. بدءاً من Mockito 5.x مع mockito-inline، هذه الإمكانية مدمجة مباشرة: mockStatic(ClassName.class) يسمح بـ mocking الطرق الثابتة دون مكتبات إضافية.

اختبار ViewModel مع Mockito

سيناريو نموذجي: ViewModel يستدعي طريقة من المستودع ويحول النتيجة إلى حالة واجهة المستخدم. Mockito يستبدل المستودع، ويختبر الاختبار أن ViewModel يعالج بشكل صحيح كلاً من الاستجابة الناجحة والخطأ. عند استخدام Clean Architecture، يتم إنشاء mocks لكل طبقة: DataSource و Repository و UseCase — وهذا يسمح باختبار كل طبقة بشكل معزول.

  • حالة النجاح — when(repo.getData()).thenReturn(Result.success(data)) → التحقق من state = Success(data).
  • حالة الخطأ — when(repo.getData()).thenReturn(Result.error(exception)) → التحقق من state = Error(message).
  • حالة التحميل — verify أن ViewModel قام بتعيين isLoading = true قبل استدعاء المستودع.

الأسئلة الشائعة

ما الفرق بين Mockito و MockK؟

Mockito هي مكتبة لـ Java و Kotlin تستخدم الوكلاء والانعكاس. MockK هي مكتبة Kotlin-first مع دعم للروتينات المساعدة ووظائف التوسيع والفئات النهائية دون تكوين إضافي.

كيفية إنشاء mock لفئة نهائية في Mockito؟

بدءاً من Mockito 2.1، يتم دعم mocking الفئات النهائية عبر opt-in. في الإصدار 5.x (mockito-inline)، هذا مفعل افتراضياً. يكفي إضافة التبعية mockito-inline واستخدام طريقة mock() القياسية.

ما هو Spy في Mockito؟

Spy هو mock جزئي يقوم افتراضياً باستدعاء الطرق الحقيقية ولكنه يسمح بتجاوز بعضها عبر when().thenReturn(). Spy مفيد لاختبار الكود القديم عندما لا يمكن إعادة كتابة الفئة بأكملها.

ما الفرق بين thenReturn و thenAnswer؟

thenReturn يعيد دائماً نفس القيمة بغض النظر عن الوسائط. thenAnswer يحسب القيمة المعادة بناءً على الاستدعاء — وسائط الاستدعاء و mock نفسه والحالة. للاستجابات الديناميكية، استخدم دائماً thenAnswer.

لماذا يعتبر verify مهماً للاختبارات مع mocks؟

Verify يتحقق ليس فقط من النتيجة ولكن أيضاً من العملية — حقيقة الوصول إلى التبعية. هذا أمر بالغ الأهمية للخدمات التي يجب أن تحفظ البيانات أو ترسل الإشعارات. بدون verify، لن يكتشف الاختبار أن طريقة معينة لم تستدع save() أو send().

الخلاصة

  • Mockito — مكتبة لإنشاء كائنات mock، المعيار الفعلي لـ mocking في Java و Kotlin.
  • يتم إنشاء mocks عبر mock(Class) أو التعليق التوضيحي @Mock مع MockitoExtension.
  • Stubbing عبر when().thenReturn() يحدد سلوك طرق mock.
  • Verify يتحقق من حقيقة وعدد استدعاءات طرق mock بالوسائط المحددة.
  • @InjectMocks يحقن تلقائياً mocks في الكائن المختبر.
  • تكامل Android — يُستخدم Mockito لاختبار ViewModel و Repository و UseCase.
  • ArgumentCaptor يلتقط وسائط الاستدعاء للتحقق التفصيلي من حقول الكائن.

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا