موكيتو وكيفية طبخه

حول المادة


هنا هو دليل آخر ل Mockito. في ذلك ، من جهة ، حاولت أن أصف وظيفة هذه المكتبة حتى يتمكن القارئ الذي لا يعرفها على الفور من فرصة استخدامها بالكامل ، وليس مجرد فكرة عامة عنها. من ناحية أخرى ، أردت أن أجعله مضغوطًا بما يكفي ومنظمًا حتى أتمكن من قراءته سريعًا بالكامل وأجد فيه شيئًا ما قرأته سريعًا ، لكن نسيته. بشكل عام ، هذه المقالة ، والتي من شأنها أن تكون مفيدة لي ، عندما جئت للتو من هذه المكتبة ولم أفهم حقًا كيف تعمل.


أفترض أنه يمكن أن يكون مفيدًا لي الآن - وأحيانًا أنسى بعضًا من هذا الأمر ، ومن الملائم للغاية أن أذكر المواد ليس وفقًا للوثائق الرسمية أو مقالات الأشخاص الآخرين ، ولكن وفقًا لمقالتي الخاصة ، دعنا نقول الموجز. في الوقت نفسه ، حاولت إنشاء النص بحيث كان مناسبًا في المقام الأول للتعرف على Mockito من البداية ، وفي بعض الأماكن قمت بتحليلها بالتفصيل على ما يبدو أشياء واضحة - لم تكن كلها واضحة بالنسبة لي منذ البداية.


المحتويات:


  1. موكيتو: ما هو ولماذا هو ضروري
  2. البيئة ، الإصدارات والحيوانات التجريبية
  3. وهمية والتجسس
  4. إدارة السلوك
    1. تحديد شروط الاتصال
    2. تحديد نتائج الاتصال
  5. تتبع طريقة المكالمات
  6. كائنات وهمية كقيم قيم وشروح موكيتو
  7. التراجع عن السلوك إلى الجلسات الافتراضية و Mockito
  8. ماذا بعد؟

موكيتو: ما هو ولماذا هو ضروري


باختصار ، Mockito هو إطار كعب.


كما تعلم ، عند اختبار الكود (بشكل أساسي اختبار الوحدة ، ولكن ليس فقط) ، يحتاج العنصر قيد الاختبار غالبًا إلى توفير مثيلات للفصول التي يجب استخدامها عند العمل. ومع ذلك ، غالبًا ما لا يتعين عليهم أن يعملوا بشكل كامل - بل على العكس ، يجب عليهم التصرف بطريقة محددة بدقة ، بحيث يكون سلوكهم بسيطًا ويمكن التنبؤ به تمامًا. يطلق عليهم بذرة. للحصول عليها ، يمكنك إنشاء تطبيقات اختبار بديلة للواجهات ، ورث الفئات اللازمة مع إعادة تعريف الوظيفة ، وما إلى ذلك ، ولكن كل هذا غير مريح للغاية ومتكرر ومحفوف بالأخطاء. الحل الأكثر ملاءمة من جميع النواحي هو الأطر المتخصصة لإنشاء بذرة. واحد من هؤلاء (وربما الأكثر شهرة لجافا) هو Mockito.


يتيح لك Mockito إنشاء ما يسمى بالسخرية (سطر يشبه أساس كعب الروتين المطلوب) لأي سطر فئة بسطر واحد من التعليمات البرمجية. لمثل هذا النوع من السخرية ، مباشرة بعد الإنشاء ، يكون السلوك الافتراضي المعين مميزًا (تُرجع جميع الطرق قيمًا معروفة مسبقًا - عادةً ما تكون هذه القيمة null أو 0 ). يمكنك إعادة تعريف هذا السلوك بالطريقة التي تريدها ، والتحكم فيه بالقدر المناسب من التفاصيل ، وهكذا. نتيجة لذلك ، يصبح النموذج روتينًا مع الخصائص المطلوبة. أدناه سأناقش بالتفصيل كيفية القيام بذلك.


ألاحظ أنه يمكن أيضًا إنشاء نماذج وهمية لتلك الفئات ، حيث لا يمكنك إنشاء مثيل جديد بالفعل ، على وجه الخصوص ، مع فئات خاصة حصريًا مثل فئات مفردة وفئة فائدة ، مع الحد الأدنى من تكوين إطار العمل والتعدادات.


البيئة ، الإصدارات والحيوانات التجريبية


عند كتابة هذا المقال ، استخدمت:


  • Mockito: 'org.mockito: mockito-core: 2.24.0' (أحدث إصدار ثابت وقت كتابة هذا التقرير)
  • TestNG: 'org.testng: testng: 6.14.3' كإطار اختبار
  • AssertJ: 'org.assertj: assertj-core: 3.11.1' كأداة للتحقق من الصحة
  • لومبوك: 'org.projectlombok: lombok: 1.18.6' (للراحة فقط)
  • جافا 8

بالنسبة لتجاربي اللاإنسانية ، كتبت واجهة هذه الخدمة التي توفر الوصول إلى بيانات معينة.


 public interface DataService { void saveData(List<String> dataToSave); String getDataById(String id); String getDataById(String id, Supplier<String> calculateIfAbsent); List<String> getData(); List<String> getDataListByIds(List<String> idList); List<String> getDataByRequest(DataSearchRequest request); } 

وهذا (فليكن من أجل النظام) رمز فئة الطلب تمريرها إلى آخر أساليب واجهة.


 @AllArgsConstructor @Getter class DataSearchRequest { String id; Date updatedBefore; int length; } 

يتم تحديد وحدات البيانات بواسطة المعرف ولها بعض الخصائص ، ولكن مباشرة في النموذج الذي يتم إرجاعها بواسطة الخدمة ، فهي سلاسل ، وليس بعض الكائنات الأكثر تعقيدًا. لا أفتقد أي شيء مهم ، والأمثلة أبسط وأكثر وضوحًا.


سألاحظ على الفور: في الأمثلة أدناه ، أدعو مباشرة الأساليب المتجاهلة للكائنات الصورية الخاصة بي من أجل الوضوح ، لكن مع الاختبار الحقيقي ، الفكرة ليست على الإطلاق! في هذا الاختبار ، سأقوم دائمًا بما يلي:


  • تكوين وهمية خدمي حسب الحاجة ؛
  • نقلها (على الأرجح ، عبر المُنشئ) إلى مثيل لفئة أخرى تستخدمه (لنفترض أنه يحتوي على نوع من منطق الأعمال باستخدام البيانات المقدمة من DataService ) ، والتي DataService بالفعل ؛
  • تمكين وظيفة الفئة اختبار والتحكم في النتائج ؛
  • إذا لزم الأمر ، كنت أتحكم في عدد وترتيب المكالمات إلى طريقة (طرق) وهمية بلدي ، والتي كان ينبغي أن يكون دعا من قبل الطبقة المختبرة نتيجة للإجراء السابق.

وهمية والتجسس


الفئة المركزية من Mockito ، والتي يُفترض من خلالها الوصول إلى معظم الوظائف ، هي في الواقع فئة تُدعى Mockito (هناك أيضًا فئة BDDMockito التي توفر نفس الإمكانيات في نموذج أكثر ملاءمة ل BDD ، لكنني لن أتوقف عند هذا الحد) . يتم تنفيذ الوصول إلى وظيفة من خلال أساليب ثابتة.


لإنشاء نسخة وهمية من فئة DataService ، لا بد لي من القيام بما يلي:


 DataService dataServiceMock = Mockito.mock(DataService.class); 

تم - حصلت على مثيل للفئة التي احتاجها. سيتم قبولها بواسطة أي طريقة أو مُنشئ يتطلب معلمة من هذا النوع (على سبيل المثال ، مُنشئ الفئة التي أريد اختبارها). حتى إذا كان فحص الإدمان ينتظره لاحقًا ، فسوف ينجح في ذلك: ليس فقط instanceof DataService سيعود true ، ولكن أيضًا dataServiceMock.getClass() - وهي DataService.class . بطريقة ما ، تبين أن التمييز بين كائن وهمي وبرمجي عن كائن عادي مهمة صعبة إلى حد ما ، وهو أمر منطقي: بعد كل شيء ، الهدف الأول هو مجرد تمييزه عن الآخر. ومع ذلك ، Mockito لديه أداة لهذا - أسلوب Mockito.mockingDetails . بتمريره كائن تعسفي ، أحصل على كائن من الفئة MockingDetails . أنه يحتوي على معلومات حول ما يمثله هذا الكائن من وجهة نظر Mockito: ما إذا كانت وهمية ، تجسس (انظر أدناه) ، وكيف تم استخدامه ، وكيف تم إنشاؤه وهلم جرا.


من الجدير بالملاحظة هو الموقف عندما أحاول إنشاء نموذج وهمية للفئة النهائية أو نسخة وهمية من التعداد أو لتجاوز سلوك الطريقة النهائية. في هذه الحالة ، مع السلوك الافتراضي لـ Mockito ، يرفض الرمز أعلاه العمل ، مع الإشارة إلى هذا الظرف بالتحديد. ومع ذلك ، يمكن تغيير ذلك - فقط قم بإنشاء ملف test/resources/mockito-extensions/org.mockito.plugins.MockMaker في المشروع (باستخدام الجهاز القياسي لشجرة دليل المشروع) وأدخل السطر فيه:


 mock-maker-inline 

بعد ذلك ، يمكنك محاكاة الطبقات النهائية والتعدادات بالطريقة المعتادة ، وكذلك تجاوز الطرق النهائية.


النموذج الذي حصلت عليه في العمل لا يميزه قدر الإمكان: لن يكون لأي طريقة واحدة أي تأثير على أي شيء ، وستكون القيمة التي تم إرجاعها null لأنواع الكائنات و 0 للأنواع البدائية. يرجى ملاحظة: إذا كانت الطريقة تُرجع مجموعة ، فلن يُرجع النموذج الافتراضي قيمة null ، ولكن سيُظهر مثيلات فارغة. على سبيل المثال ، بالنسبة List سيتحول هذا إلى قائمة LinkedList فارغة LinkedList بغض النظر عن الطريقة الحقيقية التي يجب أن تُرجع إليها. لكن كقيم المصفوفات ، بدائية أو كائن ، أشعر null . يمكن تغيير السلوك الافتراضي (وليس فقط) باستخدام وظيفة فئة MockSettings ، ولكن هذا نادرًا ما يكون ضروريًا.


بطريقة أو بأخرى ، لن أحتاج في معظم الحالات إلى السلوك الافتراضي ، وفي القسم التالي سأحلل بالتفصيل كيفية تعيين ما هو مطلوب بدلاً من ذلك.


ومع ذلك ، ماذا لو كنت أرغب في استخدام كائن فئة حقيقية مع الوظيفة المتاحة كعنصر كعب ، وإعادة تحديد عملية جزء فقط من أساليبها؟ إذا كنا نتحدث عن اختبار الوحدة ، فإن مثل هذه الحاجة عادة (ولكن ليس دائمًا) تشير إلى أن المشروع لا يتناسب تمامًا مع التصميم ، ومن حيث المبدأ ، لا يوصى بذلك. ومع ذلك ، هناك حالات عندما لا يمكن تجنب هذا لسبب ما. لهذه الحالة ، Mockito لديه جاسوس يسمى "جواسيس". على عكس mocks ، يمكن إنشاؤها بناءً على كلٍ من الفئة والكائن النهائي:


 DataService dataServiceSpy = Mockito.spy(DataService.class); // or DataService dataService = new DataService(); dataServiceSpy = Mockito.spy(dataService); 

عند إنشاء جاسوس بناءً على فصل دراسي ، وإذا كان نوعه عبارة عن واجهة ، فسيتم إنشاء كائن وهمي عادي ، وإذا كان النوع عبارة عن فئة ، فسيحاول Mockito إنشاء مثيل باستخدام المُنشئ الافتراضي (بدون معلمات). وفقط في حالة عدم وجود مُنشئ من هذا القبيل ، سيحدث خطأ ولن ينجح الاختبار.


يشبه سلوك كائنات التجسس افتراضيًا سلوك مثيل فئة عادي ، ومع ذلك فهي توفر لي نفس إمكانيات الكائنات وهمية: فهي تسمح لي بإعادة تعريف سلوكها ومراقبة استخدامها (راجع الأقسام التالية). نقطة مهمة: التجسس ليس غلافًا حول المثيل الذي تم إنشاؤه عليه! لذلك ، لن يؤثر استدعاء طريقة التجسس على حالة المثيل الأصلي.


إدارة السلوك


لذلك ، على كيفية الحصول على وهمية أو تجسس لفعل ما أحتاج إليه. علاوة على ذلك ، سأكتب دائمًا ببساطة "وهمية" في كل مكان - وهذا يعني "وهمية أو تجسس" ، ما لم ينص صراحة على خلاف ذلك.


بشكل عام ، فإن التحكم في سلوك الجسم الزائف يأتي إلى مفهوم واحد واضح: عندما يتأثر الصخر الزيتي بهذه الطريقة (أي ، وقد استُدعيت هذه الطريقة بهذه الحجج وكذا) ، ينبغي أن تستجيب بهذه الطريقة ومثل هذه الطريقة. يحتوي هذا المفهوم على تطبيقين داخل فئة Mockito - التطبيق الرئيسي ، الموصى به من قِبل المطورين للاستخدام حيثما كان ذلك ممكنًا ، والآخر بديل ، حيث يتم استخدام التطبيق الرئيسي في مكان غير مناسب.


يعتمد التطبيق الرئيسي على أسلوب Mockito.when . تأخذ هذه الطريقة "معلمة" استدعاء للطريقة OngoingStubbing للكائن وهمية (وبهذه الطريقة يتم إصلاح الإجراء الذي تم اكتشافه) وإرجاع كائن من النوع OngoingStubbing ، والذي يسمح باستدعاء إحدى أساليب عائلة Mockito.then... (هذه هي الطريقة التي يتم بها ضبط رد الفعل على هذا التأثير). معًا ، في أبسط الحالات ، يبدو الأمر كما يلي:


 List<String> data = new ArrayList<>(); data.add("dataItem"); Mockito.when(dataService.getAllData()).thenReturn(data); 

بعد هذه العملية ، عن طريق استدعاء الأسلوب getAllData() على كائن getAllData() ، أحصل على الكائن المحدد في السطر الأول من القائمة.


هنا ، قد يعطي الحدس المألوف "الموجه نحو الكائنات" نوعًا من الخلل ، لذلك فإن الأمر يستحق مزيدًا من التفصيل. من وجهة نظر بناء جملة Java ، فإن القيمة التي يتم تمريرها إلى when الطريقة كمعلمة هي ، بالطبع ، القيمة التي يتم إرجاعها بواسطة الطريقة التي تم تجاوزها. بالنسبة للسخرية ، هذه قيمة فارغة ؛ بالنسبة للتجسس ، هذه هي القيمة التي يتم إرجاعها بواسطة طريقة الكائن الحقيقي. ولكن بفضل التمثيل السحري "تحت غطاء محرك السيارة" في Mockito ، ستعمل الطريقة when تعمل بشكل طبيعي (ولن تتعطل عند إطلاقها مع وجود خطأ) فقط إذا كانت استدعاء أسلوب الكائن الصوري داخل الأقواس بعد when .


غالبًا ما تعمل الإيديولوجية المماثلة عند تحديد سلوك المستهزئ في موكيتو: عن طريق استدعاء طريقة (كائن وهمية أو فئة من Mockito ) ، أحاول ألا أحصل على القيمة المرجعة Mockito ، لكن تؤثر بطريقة أو بأخرى على المكالمة المحتملة لطريقة الكائن وهمية الذي أعمل معه: حدد حدودها ، تعيين النتيجة ، إنشاء مراقبة لتحدياتها وهلم جرا. أعترف أن الأمر يبدو ضبابيًا قليلاً ، ويبدو أنه عند الاصطدام الأول غريب ، لكنك ، بعد أن اكتشفته ، سرعان ما بدأت تشعر أن هذا النهج طبيعي تمامًا في سياق التعامل مع بذرة.


تطبيق بديل لربط الشرط ونتيجة المكالمة هو أساليب عائلة Mockito.do... تسمح لك هذه الطرق بضبط السلوك بدءًا من نتيجة المكالمة وإعادة كائن من فئة Stubber ، والذي يمكنك بالفعل ضبط الشرط عليه. يبدو الارتباط نفسه كما هو مذكور أعلاه بهذه الطريقة:


 List<String> data = new ArrayList<>(); data.add("dataItem"); Mockito.doReturn(data).when(dataService).getData() 

ما هو الفرق ، لماذا يعتبر الربط من خلال Mockito.when يعتبر الأفضل وعندما لا يزال يتعين عليك استخدام أساليب Mockito.do... ؟ يرجى ملاحظة: عند التنفيذ الأول ، عند تعيين سلوك الطريقة (في هذه الحالة ، getAllData() ) ، يتم تنفيذ المكالمة إلى الإصدار الذي لم يتم إعادة تعريفه أولاً ، وبعد ذلك فقط ، في أحشاء Mockito ، يحدث تخطي. في الحالة الثانية ، لا يتم إجراء مثل هذه المكالمة - Stubber.when تمرير الأسلوب Stubber.when مباشرة إلى طريقة Stubber.when ، ويتم Stubber.when كائن من نفس النوع يتم إرجاعه بواسطة هذه الطريقة ولكن ذات طبيعة مختلفة بأسلوب قابل للاستبدال. هذا الاختلاف يحدد كل شيء. الربط عبر Mockito.do... لا يتحكم مطلقًا في مرحلة التجميع بالطريقة التي يمكن إعادة تعريفها والتي سأتصل بها وما إذا كانت متوافقة في الكتابة مع قيمة الإرجاع المحددة. لذلك ، عادة Mockito.when الأفضل - لا يمكن أن يكون هناك خطأ في هذا. ولكن قد تكون هناك حالات عندما أرغب في تجنب الاتصال بأسلوب متجاوَز ، فبالنسبة إلى وهمي تم إنشاؤه حديثًا ، تكون هذه المكالمة مقبولة تمامًا ، لكن إذا كنت قد قمت بالفعل بإعادة تعريف هذه الطريقة أو تعاملت مع الجاسوس ، فقد يكون ذلك غير مرغوب فيه ، ورفض استثناء لن يسمح بإعادة التعريف الضرورية على الإطلاق . وهنا ملزمة من خلال Mockito.do... يأتي إلى Mockito.do...


موقف آخر حيث لا يمكنك الاستغناء عن أساليب Mockito.do... هو تجاوز الطريقة التي Mockito.when void : المعلمة Mockito.when المعلقة لا يمكن أن تعمل مع مثل هذه الطريقة. Mockito.doReturn ، بطبيعة الحال ، Mockito.doReturn عن العمل ، ولكن هناك Mockito.doThrow و Mockito.doAnswer ونادراً ما يكون Mockito.doNothing كافية.


بعد ذلك ، سأدرس بالتفصيل بعض الشيء كيفية تعيين شروط ونتائج المكالمات. سوف أفكر ملزمة فقط من خلال Mockito.when - طريقة بديلة تقريبا مماثلة تماما في التعامل معها.


تحديد شروط الاتصال


المثال أعلاه يتعلق بطريقة بدون معلمات ، وحالة المكالمة المرتبطة أمر واحد ممكن - حقيقة المكالمة. بمجرد ظهور المعلمات ، يصبح الموقف أكثر تعقيدًا. كحد أدنى ، لاستدعاء طريقة أقوم بضبط سلوكها ، أحتاج إلى تمرير شيء إليها. ولكن هناك شيئًا آخر أكثر أهمية: فقد يتضح أنني لا أريد دائمًا الحصول على رد فعل معين ، ولكن فقط عندما أسميها بمعلمات تفي بمتطلبات معينة. DataService هذه الطريقة:


 String getDataItemById(String id) { // some code... } 

إذا كنت بحاجة إلى تعيين استجابة لأي مكالمة لهذه الطريقة بغض النظر عن الوسيطات ، فيجب أن Mockito.any طريقة Mockito.any :


 Mockito.when(dataService.getDataItemById(any())) .thenReturn("dataItem"); 

إذا كنت تريد أن يستجيب النموذج وهمية فقط بقيمة معينة من الوسيطة ، يمكنك استخدام هذه القيمة مباشرة أو أساليب Mockito.eq (إذا كنا نتحدث عن التكافؤ) أو Mockito.same (إذا كانت مقارنة الارتباط مطلوبة):


 Mockito.when(dataService.getDataItemById("idValue")) .thenReturn("dataItem"); // or Mockito.when(dataService.getDataItemById(Mockito.eq("idValue"))) .thenReturn("dataItem"); 

وإذا أردت أن تفي الوسيطة ببعض المتطلبات ، فهناك عدد من الأساليب الثابتة المتخصصة من نفس فئة Mockito (على سبيل المثال ، يمكن التحقق من السلاسل بحثًا عن محتويات في بداية أو في نهاية تسلسل معين من الأحرف ، ومطابقة الأنماط ، وما إلى ذلك). هناك أيضًا طريقة عامة Mockito.argThat (ونظائرها للأنواع البدائية) التي تقبل تنفيذ واجهة ArgumentMatcher الوظيفية:


 Mockito.when(dataService.getDataById( Mockito.argThat(arg -> arg == null || arg.length() > 5))) .thenReturn("dataItem"); 

تسمح لك فئات ArgumentMatchers و ArgumentMatchers بالعمل مع بعض التطبيقات المفيدة الجاهزة لهذه الواجهة. على سبيل المثال ، تتيح لك ExtraMatchers.or و ExtraMatchers.and الجمع بين المطابقات الأخرى (ملاحظة: الأساليب الثابتة لهذه الفئات لا تُرجع مثيلات المطابقات ، لكن فقط الوصول إليها!)


لنفس الطريقة ، يمكنك ضبط السلوك عدة مرات مع متطلبات مختلفة للوسائط ، وجميع نماذج السلوك المحددة بهذه الطريقة ستعمل في وقت واحد. بالطبع ، في بعض الحالات ، قد تتقاطع - على سبيل المثال ، سأطلب إرجاع نتيجة واحدة عندما تكون قيمة int للمعلمة أقل من 5 والآخر عند تلقي القيمة الزوجية. في هذه الحالة ، يكون للسلوك المحدد لاحقًا الأسبقية. لذلك ، عند تحديد أنماط السلوك المعقدة ، يجب أن تبدأ بمتطلبات أضعف (في الحد - any() ) ثم تنتقل إلى أنماط أكثر تحديداً.


عند العمل مع أساليب تحتوي على أكثر من وسيطة واحدة ، يتم الجمع بين المتطلبات المحددة وفقًا للمنطقية AND ، أي للحصول على النتيجة المعطاة ، يجب أن تفي كل الوسائط بالمتطلبات المحددة. لم أجد طريقة لضبط طريقة تعسفية للجمع بينهما ، على الرغم من وجودها.


بالإضافة إلى ذلك ، عند تحديد سلوك مثل هذه الطريقة ، لا يمكن الجمع بين أساليب Mockito الثابتة Mockito ونقل القيم المباشر. استخدم Mockito.eq أو Mockito.same .


تحديد نتائج الاتصال


بعد استدعاء طريقة الكائن الصوري ، يجب أن يستجيب الكائن للمكالمة. تتمثل العواقب المحتملة الرئيسية في إعادة النتيجة وإلقاء استثناء ، وعلى هذه الخيارات بالتحديد ، تم تصميم مجموعة أدوات Mockito في المقام الأول.


في أبسط الحالات الموضحة أعلاه ، فإن استجابة المكالمة هي إرجاع قيمة. سأقدم رمزه مرة أخرى:


 List<String> data = new ArrayList<>(); data.add("dataItem"); Mockito.when(dataService.getAllData()).thenReturn(data); 

يرجى ملاحظة: يمكنك فقط إرجاع كائن ؛ لا توجد طرق منفصلة للأوليات. لذلك ، إذا كانت الطريقة تُرجع القيمة البدائية ، في مثل هذه الحالة ، ستحدث un / boxing. في معظم الحالات ، لا يتداخل هذا ، ولكن إذا فكر المترجم بخلاف ذلك ، فسوف يتعين عليك الاتفاق معه بطريقة أو بأخرى ... أو تحمل تحذيراته.


استثناءات الرمي ليست أصعب:


 Mockito.when(dataService.getDataById("invalidId")) .thenThrow(new IllegalArgumentException()); 

هناك طريقة أخرى: يمكنك إنشاء كائن استثناء ورميه مباشرة ، أو يمكنك تزويد Mockito بفئة استثناء فقط بحيث يتم إنشاؤها تلقائيًا:


 Mockito.when(dataService.getDataById("invalidId")) .thenThrow(IllegalArgumentException.class); 

في كلتا الحالتين ، يسمح لك بناء الجملة باستخدام الاستثناءات والتحقق منها ، لكن Mockito لن يسمح لك بإجراء مثل هذا الاختبار إذا كان نوع الاستثناء لا يتطابق مع الطريقة التي أرغب في فرض هذا الاستثناء عليها.


عند استخدام فئة كمعلمة ، يتم تجاهل المنشئات (حتى بدون المعلمات) ، وكذلك التهيئة المباشرة للحقل - يتم إنشاء الكائن بتجاوزها (بعد كل شيء ، هذا هو Mockito!) ، بحيث تكون جميع حقول الاستثناء الذي تم طرحه null . لذلك ، إذا كان محتوى الاستثناء مهمًا لك (على سبيل المثال ، بعض حقول type التي لها قيمة افتراضية) ، فسيتعين عليك التخلي عن هذه الطريقة وإنشاء استثناءات يدويًا.


تكون خيارات رد الفعل هذه مناسبة ، إذا كنت بحاجة دائمًا إلى إرجاع قيمة معينة ، أو دائمًا نفس القيمة للنتيجة ، أو الرد دائمًا على نفس الاستثناء ، وفي معظم الحالات تكون هذه القدرات كافية تمامًا. لكن ماذا لو كانت المرونة مطلوبة؟ افترض أن طريقتي تقبل مجموعة من القيم ، وتُرجع مجموعة أخرى من القيم المرتبطة من الأول إلى واحد (على سبيل المثال ، الحصول على مجموعة من كائنات البيانات بواسطة مجموعة معرفاتها) ، وأريد استخدام هذا الكائن وهمية بشكل متكرر مع مجموعات مختلفة من المدخلات في الاختبار البيانات ، والحصول في كل مرة النتيجة المقابلة. يمكنك بالطبع وصف رد الفعل بشكل منفصل لكل مجموعة محددة من المعلمات ، ولكن هناك حلًا أكثر ملاءمة - طريقة Mockito.thenAnswer ، ويعرف أيضًا باسم Mockito.then . يقبل تطبيق الواجهة الوظيفية Answer ، الطريقة الوحيدة منها هي تلقي كائن من فئة InvocationOnMock . من الأخير ، يمكنني طلب معلمات استدعاء الأسلوب (واحدة تلو الأخرى أو كلها مرة واحدة في شكل صفيف) والتصرف معهم كما أريد. على سبيل المثال ، يمكنك الحصول على قيمة مقابلة لكل عنصر من عناصر مجموعتي ، وتشكيل مجموعة جديدة منها وإعادتها (ملاحظة: يتم إرجاع النتيجة المرغوبة ببساطة ، ولا تتم كتابتها في حقل ما من كائن المعلمة ، كما قد تتوقع):


 Mockito.when(dataService.getDataByIds(Mockito.any())) .thenAnswer(invocation -> invocation .<List<String>>getArgument(0).stream() .map(id -> { switch (id) { case "a": return "dataItemA"; case "b": return "dataItemB"; default: return null; } }) .collect(Collectors.toList())); 

من الناحية الأيديولوجية ، هذا شيء مثل كتابة نموذج لطريقة حقيقية: الحصول على المعلمات ومعالجتها وإرجاع النتيجة. - , - , , , mock- .


Answer , , — , AnswersWithDelay , ReturnsElementsOf . .


: InvocationOnMockObject[] , generic-.


thenCallRealMethod . . mock-, spy-. mock , , - null . spy thenCallRealMethod spy ; , - .


thenAnswer : InvocationOnMock callRealMethod() — , "" - .


OngoingStubbing OngoingStubbing , , , . , . thenReturn thenThrow , varargs. .


 Mockito.when(dataService.getDataById("a")) .thenReturn("valueA1", "valueA2") .thenThrow(IllegalArgumentException.class); 

"valueA1 , — "valueA2 ( ), ( ) IllegalArgumentException .



: (mock' ), . , : , , . verify .


, , :


 Mockito.verify(dataService).getDataById(Mockito.any()); 

, getDataById , , . , Mockito, when , , , mock-. , , , when , — mock', (. ).


:


 Mockito.verify(dataService, Mockito.times(1)) .getDataById(Mockito.any()); 

Mockito.times ; Mockito.never . Mockito.atLeast ( Mockito.atLeastOnce 1) Mockito.atMost , , Mockito.only , , mock- (. . ).


, Mockito , VerificationAfterDelay VerificationWithTimeout , Mockito.after Mockito.timeout . على سبيل المثال:


 Mockito.verify(dataService, Mockito.after(1000).times(1)) .getDataById(Mockito.any()); 

, mock , , , . . after timeout , , , — , . , timeout — . VerificationWithTimeout never atMost : .


, Mockito.any() . , , — Mockito , , . Mock- , , , , :


 dataService.getDataById("a"); dataService.getDataById("b"); Mockito.verify(dataService, Mockito.times(2)).getDataById(Mockito.any()); Mockito.verify(dataService, Mockito.times(1)).getDataById("a"); Mockito.verify(dataService, Mockito.never()).getDataById("c"); dataService.getDataById("c"); Mockito.verify(dataService, Mockito.times(1)).getDataById("c"); Mockito.verifyNoMoreInteractions(dataService); 

verifyNoMoreInteractions ( verifyZeroInteractions ) — - ( verify ) mock- — . : varargs, , , !


, , , . , InOrder :


 InOrder inOrder = Mockito.inOrder(dataService); 

varargs; — mock- , InOrder . verify , Mockito.verify :


 inOrder.verify(dataService, times(2)).saveData(any()); inOrder.verify(dataService).getData(); 

, saveData , getData . , InOrder , — .


, , — , . - , , — , , . ArgumentCaptor capture() . على سبيل المثال:


 DataSearchRequest request = new DataSearchRequest("idValue", new Date(System.currentTimeMillis()), 50); dataService.getDataByRequest(request); ArgumentCaptor<DataSearchRequest> requestCaptor = ArgumentCaptor.forClass(DataSearchRequest.class); Mockito.verify(dataService, times(1)).getDataByRequest(requestCaptor.capture()); assertThat(requestCaptor.getAllValues()).hasSize(1); DataSearchRequest capturedArgument = requestCaptor.getValue(); assertThat(capturedArgument.getId()).isNotNull(); assertThat(capturedArgument.getId()).isEqualTo("idValue"); assertThat(capturedArgument.getUpdatedBefore()).isAfterYear(1970); assertThat(capturedArgument.getLength()).isBetween(0, 100); 

ArgumentCaptor , , ArgumentCaptor . getValue() , getAllValues() — . , , .


Mock- Mockito


, mock- , — @Mock - :


 MockitoAnnotations.initMocks(this); 

( , mock', )


spy @Spy@Mock … spy , , ? , — spy .


@Captor ArgumentCaptor — , , .


@InjectMocks . - Mockito, . mock- , . , . - , null , - . ( ) dependency injection.


Mockito


, : mock (spy, argument captor...), , , . , mock' — , . JUnit , , TestNG — . , , mock' , , , . . , , — , .


, mock- . TestNG @BeforeMethod ( @AfterMethod ). mock' , , ( JUnit — @Before ).


, , — Mockito.reset Mockito.clearInvocations . varargs, mock'. , . : (, ) , / mock' , — . , mock' . . , , .


(, ) — MockitoAnnotations.initMocks(this); . "" , Mockito.


— Mockito. . mock- , ( mock' ). , MockitoSession , . TestNG:


 @Mock DataService dataService; MockitoSession session; @BeforeMethod public void beforeMethod() { session = Mockito.mockitoSession() .initMocks(this) .startMocking(); } @Test public void testMethod() { // some code using the dataService field } @AfterMethod public void afterMethod() { session.finishMocking(); } 

, — , "" (, ) , .


?


Mockito: mock spy-, . , . , , :


  • Mockito mock- MockSettings ( — , mock' - );
  • mock-, MockingDetails ;
  • BDDMockito Mockito ;
  • ( JUnit Mockito, ).

Mockito . javadoc' Mockito .


, , .

Source: https://habr.com/ru/post/ar444982/


All Articles