محاكاة الـ Beans باستخدام @MockBean
محاكاة الـ Beans باستخدام @MockBean
كل تطبيق Spring Boot هو رسم بياني من الـ beans المتعاونة. عندما تكتب اختبار شريحة (slice test) — مثل @WebMvcTest لأحد الـ controllers — لا يُهيّئ Spring سوى جزء من هذا الرسم. الـ beans التي لا يحمّلها الاختبار (services، repositories، clients خارجية) لا تزال تظهر كاعتماديات. @MockBean يحل هذه المشكلة تحديدًا: فهو يُسجّل كائن mock من Mockito بوصفه bean داخل سياق التطبيق الاختباري، مُرضيًا الاعتمادية دون تحميل التنفيذ الحقيقي.
الفرق بين @MockBean و @Mock
تنشئ الإضافة التوضيحية @Mock في Mockito (أو Mockito.mock()) كائن mock يعيش خارج سياق Spring تمامًا. تربطه بالكلاس المُختبَر يدويًا عادةً عبر المُنشئ أو setter. هذا يعمل جيدًا في اختبارات الوحدة الخالصة حيث تُنشئ الكلاس بنفسك.
أما @MockBean فيأمر دعم اختبار Spring بـاستبدال (أو إنشاء) bean من النوع المعطى داخل ApplicationContext. أي bean آخر يحقن ذلك النوع سيتلقى الـ mock تلقائيًا — دون الحاجة إلى ربط يدوي. تُنشئ Mockito الـ mock الأساسي، لذا تعمل جميع واجهات when(…).thenReturn(…) وverify(…) المألوفة بالضبط كما تتوقع.
@Mock في اختبارات الوحدة الخالصة (بدون سياق Spring). استخدم @MockBean في كل مرة تحتاج فيها Spring لربط الاعتماديات نيابةً عنك — داخل @WebMvcTest أو @DataJpaTest أو شرائح @SpringBootTest.
مثال واقعي: شريحة Controller مع Service محاكى
لنفترض أن لديك OrderController يُفوّض إلى OrderService. تحميل الـ OrderService الحقيقي سيستدعي repository من JPA واتصالًا بقاعدة بيانات وربما عميل بريد إلكتروني. لا مكان لأيٍّ من ذلك في اختبار controller. إليك النمط الكامل:
لاحظ ثلاثة أمور: الإضافة التوضيحية على الحقل لا على المعامل؛ يُعاد ضبط الـ mock تلقائيًا قبل كل دالة اختبار؛ وأسلوب given(…).willReturn(…) (BDD) وwhen(…).thenReturn(…) (الكلاسيكي) متبادلان — اختر أحدهما والزمه.
@MockBean داخل @SpringBootTest
يمكنك أيضًا استخدام @MockBean في @SpringBootTest الكامل عندما تريد تحميل السياق بأكمله لكن عزل متعاوِن مكلف واحد — بوابة دفع خارجية أو خدمة إرسال بريد إلكتروني مثلًا.
@MockBean تُجبر Spring على بناء ApplicationContext جديد. إن كانت عشر كلاسات اختبار تُحاكي مجموعات مختلفة من الـ beans فستحصل على عشرة سياقات ويتباطأ البناء بشدة. اجمع الاختبارات التي تحتاج نفس الـ mocks في الكلاس نفسها، أو استخدم كلاس أساسية مشتركة تُعلن فيها حقول @MockBean المشتركة.
تهيئة السلوك والتحقق من التفاعلات
الـ mock الذي يُنتجه @MockBean هو mock Mockito قياسي. بالإعداد الافتراضي تعيد كل دالة "قيمة صفر" (null للكائنات، 0 للأعداد، مجموعة فارغة لأنواع المجموعات). هيّئ فقط ما يختبره الاختبار فعلًا:
@SpyBean — عندما تحتاج التنفيذ الحقيقي
أحيانًا تريد الـ bean الحقيقي لكن تحتاج إلى تهيئة أو التحقق من دالة واحدة محددة. يُغلّف @SpyBean الـ bean الحقيقي في Spring bean داخل Mockito spy. تُنفَّذ جميع الدوال بشكل طبيعي ما لم تُهيّئها بـ doReturn(…).when(spy).method(). استخدمه باعتدال — الـ spy الذي يُهيّئ معظم دواله هو في الحقيقة مجرد mock مُقنَّع ويُشير إلى خلل في التصميم.
@MockBean هو ما يُحقنه Spring؛ أما كائن Mockito.mock() العائم في كلاس الاختبار فلن يُربط بأي مكان وستكون تهيئاته بلا أثر بصمت.
تخزين السياق مؤقتًا وأثره على الأداء
يُخزّن Spring Test السياقات بحسب مفتاح التهيئة. تشارك @MockBean في ذلك المفتاح. إرشادات عملية للحفاظ على سرعة البناء:
- أعلن جميع حقول
@MockBean/@SpyBeanفي كلاس أساسية مجردة مشتركة تمتد منها كلاسات اختبار الشريحة. ستشترك جميع الكلاسات الفرعية في سياق واحد. - فضّل شرائح
@WebMvcTestعلى@SpringBootTestالكامل عندما تحتاج طبقة الويب فقط — سياقات الشرائح أرخص بكثير في البدء. - استخدم
@MockBeanفقط للـ beans التي تعتمد عليها الكلاس المُختبَرة فعلًا. محاكاة beans غير مطلوبة "للاحتياط" تُلوّث المفتاح وتُشتّت الذاكرة المؤقتة.
الخلاصة
@MockBean هو الجسر بين قوة محاكاة Mockito وحقن الاعتماديات في Spring. يُسجّل mock بوصفه bean كامل الحقوق في Spring، مما يُتيح لاختبارات الشريحة والتكامل عزل المتعاونين المُكلفين أو الخارجيين دون فقدان فائدة إطار الربط الكامل. تذكّر: استخدم @Mock في اختبارات الوحدة الخالصة، و@MockBean عندما يجب على Spring ربط الـ mock، وخطّط لتجميع كلاسات الاختبار بعناية للحفاظ على تخزين السياق مؤقتًا وسرعة بنائك.