Ініціалізація макетних об'єктів - MockIto


122

Існує багато способів ініціалізації макетного об’єкта за допомогою MockIto. Який найкращий спосіб серед них?

1.

 public class SampleBaseTestCase {

   @Before public void initMocks() {
       MockitoAnnotations.initMocks(this);
   }

2.

@RunWith(MockitoJUnitRunner.class)

[EDIT] 3.

mock(XXX.class);

підкажіть, чи є якісь інші способи, кращі за ці ...

Відповіді:


153

Для ініціалізації макетів використовуються бігун або MockitoAnnotations.initMocksстрого рівнозначні рішення. Від javadoc MockitoJUnitRunner :

JUnit 4.5 runner initializes mocks annotated with Mock, so that explicit usage of MockitoAnnotations.initMocks(Object) is not necessary. Mocks are initialized before each test method.


Перше рішення (з MockitoAnnotations.initMocks) може бути використане, коли ви вже налаштували конкретний бігун ( SpringJUnit4ClassRunnerнаприклад) у вашому тестовому випадку.

Друге рішення (разом із MockitoJUnitRunner) - це більш класичне та моє улюблене. Код простіший. Використання бігуна забезпечує велику перевагу автоматичної перевірки використання фреймворку (описаний у цій відповіді @David Wallace ).

Обидва рішення дозволяють поділитися знущами (та шпигунами) між методами тестування. У поєднанні з програмою @InjectMocksвони дозволяють дуже швидко писати одиничні тести. Знижується код глузування котла, тести читаються легше. Наприклад:

@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {

    @Mock private ArticleCalculator calculator;
    @Mock(name = "database") private ArticleDatabase dbMock;
    @Spy private UserProvider userProvider = new ConsumerUserProvider();

    @InjectMocks private ArticleManager manager;

    @Test public void shouldDoSomething() {
        manager.initiateArticle();
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        manager.finishArticle();
        verify(database).removeListener(any(ArticleListener.class));
    }
}

Плюси: код мінімальний

Мінуси: Чорна магія. IMO в основному пояснюється анотацією @InjectMocks. За допомогою цієї примітки "ви втрачаєте біль коду" (див. Чудові коментарі @Brice )


Третє рішення - створити свій макет над кожним методом тестування. Це дозволяє, як пояснив @mlk у своїй відповіді, " самодостатній тест ".

public class ArticleManagerTest {

    @Test public void shouldDoSomething() {
        // given
        ArticleCalculator calculator = mock(ArticleCalculator.class);
        ArticleDatabase database = mock(ArticleDatabase.class);
        UserProvider userProvider = spy(new ConsumerUserProvider());
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.initiateArticle();

        // then 
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        // given
        ArticleCalculator calculator = mock(ArticleCalculator.class);
        ArticleDatabase database = mock(ArticleDatabase.class);
        UserProvider userProvider = spy(new ConsumerUserProvider());
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.finishArticle();

        // then
        verify(database).removeListener(any(ArticleListener.class));
    }
}

Плюси: Ви чітко демонструєте, як працює ваш api (BDD ...)

Мінуси: код котла більше. (Знущається над створенням)


Моя рекомендація - це компроміс. Використовуйте @Mockпримітку із використанням @RunWith(MockitoJUnitRunner.class), але не використовуйте @InjectMocks:

@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {

    @Mock private ArticleCalculator calculator;
    @Mock private ArticleDatabase database;
    @Spy private UserProvider userProvider = new ConsumerUserProvider();

    @Test public void shouldDoSomething() {
        // given
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.initiateArticle();

        // then 
        verify(database).addListener(any(ArticleListener.class));
    }

    @Test public void shouldDoSomethingElse() {
        // given
        ArticleManager manager = new ArticleManager(calculator, 
                                                    userProvider, 
                                                    database);

        // when 
        manager.finishArticle();

        // then 
        verify(database).removeListener(any(ArticleListener.class));
    }
}

Плюси: Ви чітко демонструєте, як працює ваша api (Як мою ArticleManagerінстанцію). Відсутній код котла.

Мінуси: Тест не є автономним, менше больового коду


Будьте обережні, але примітки корисні, але вони не захищають вас від того, щоб створити поганий дизайн ОО (або погіршити його). Особисто, хоча я скорочую код котла, я втрачаю кодовий біль (або PITA), який є рушієм для зміни дизайну на кращий, тому я та команда звертаємо увагу на дизайн OO. Я вважаю, що слідування дизайну OO з такими принципами, як SOLID-дизайн або ідеї GOOS, є набагато важливішим, ніж вибір способу інстанції знущань.
Бріс

1
(далі) Якщо ви не бачите, як створюється цей об’єкт, ви не відчуваєте болю з цього приводу, і майбутні програмісти можуть не реагувати добре, якщо слід додати нову функціональність. У будь-якому випадку, що є спірним обома способами, я просто кажу, щоб бути обережними.
Бріс

6
НЕ ПРАВИЛЬНО, що ці два рівні. НЕ ПРАВИЛЬНО, що простіший код є єдиною перевагою використання MockitoJUnitRunner. Для отримання додаткової інформації про відмінності див. Питання на сайті stackoverflow.com/questions/10806345/… та мою відповідь на нього.
Давуд ібн Карім

2
@Gontard Так, що залежності видно, але я бачив, що код пішов не так, використовуючи цей підхід. Щодо використання Collaborator collab = mock(Collaborator.class), на мій погляд, цей спосіб, безумовно, є правильним підходом. Незважаючи на те, що це може бути багатослівним, ви можете отримати зрозумілість та рефакторіальність тестів. Обидва способи мають свої плюси і мінуси, я ще не визначився, який підхід кращий. Емівей завжди можна написати лайно і, ймовірно, залежить від контексту та кодера.
Бріс

1
@mlk Я повністю з вами згоден. Моя англійська не дуже гарна і їй не вистачає нюансів. Моя думка полягала в тому, щоб наполягати на слові UNIT.
гончар

30

Зараз існує (станом на v1.10.7) четвертий спосіб інстанціювати макети, який використовує правило JUnit4 під назвою MockitoRule .

@RunWith(JUnit4.class)   // or a different runner of your choice
public class YourTest
  @Rule public MockitoRule rule = MockitoJUnit.rule();
  @Mock public YourMock yourMock;

  @Test public void yourTestMethod() { /* ... */ }
}

JUnit шукає підкласи TestRule з анотацією @Rule і використовує їх для обгортання тестових тверджень , які надає Runner . Підсумком цього є те, що ви можете витягнути @Before методи, @After методи і навіть спробувати ... ловити обгортки в правила. Ви навіть можете взаємодіяти з ними в межах свого тесту, як це робить ExpectedException .

MockitoRule поводиться майже так само, як MockitoJUnitRunner , за винятком того, що ви можете використовувати будь-який інший бігун, такий як Parameterized (який дозволяє вашим тестовим конструкторам приймати аргументи, щоб ваші тести можна було запустити кілька разів), або тестовий бігун Robolectric (тому його завантажувач може забезпечити заміни Java для рідних класів Android). Це робить його суворо гнучкішим у використанні в останніх версіях JUnit та Mockito.

Підсумовуючи:

  • Mockito.mock(): Пряме виклик без підтримки анотацій або перевірки використання.
  • MockitoAnnotations.initMocks(this): Підтримка анотацій, відсутність перевірки використання.
  • MockitoJUnitRunner: Підтримка анотацій та перевірка використання, але ви повинні використовувати цей бігун.
  • MockitoRule: Підтримка анотацій та перевірка використання з будь-яким бігуном JUnit.

Дивіться також: Як працює JUnit @Rule?


3
У Котліні правило виглядає так:@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
Крістан

10

Існує акуратний спосіб зробити це.

  • Якщо це модульний тест, ви можете зробити це:

    @RunWith(MockitoJUnitRunner.class)
    public class MyUnitTest {
    
        @Mock
        private MyFirstMock myFirstMock;
    
        @Mock
        private MySecondMock mySecondMock;
    
        @Spy
        private MySpiedClass mySpiedClass = new MySpiedClass();
    
        // It's gonna inject the 2 mocks and the spied object per reflection to this object
        // The java doc of @InjectMocks explains it really well how and when it does the injection
        @InjectMocks
        private MyClassToTest myClassToTest;
    
        @Test
        public void testSomething() {
        }
    }
  • EDIT: Якщо це тест на інтеграцію, ви можете зробити це (не призначене для використання таким чином у Spring. Просто покажіть, що ви можете ініціалізувати макети з різними бігунами):

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("aplicationContext.xml")
    public class MyIntegrationTest {
    
        @Mock
        private MyFirstMock myFirstMock;
    
        @Mock
        private MySecondMock mySecondMock;
    
        @Spy
        private MySpiedClass mySpiedClass = new MySpiedClass();
    
        // It's gonna inject the 2 mocks and the spied object per reflection to this object
        // The java doc of @InjectMocks explains it really well how and when it does the injection
        @InjectMocks
        private MyClassToTest myClassToTest;
    
        @Before
        public void setUp() throws Exception {
              MockitoAnnotations.initMocks(this);
        }
    
        @Test
        public void testSomething() {
        }
    }

1
Якщо MOCK також бере участь в інтеграційних тестах, чи має це сенс?
VinayVeluri

2
насправді це не буде, ваше право. Я просто хотів показати можливості Мокіто. Наприклад, якщо ви використовуєте RESTFuse, ви повинні користуватися його бігуном, щоб ви могли ініціалізувати макети з MockitoAnnotations.initMocks (це);
emd

8

MockitoAnnotations & runner були добре обговорені вище, тому я збираюся кинути свою туппененцію для нелюбимих:

XXX mockedXxx = mock(XXX.class);

Я використовую це, тому що я вважаю це трохи більш описовим, і я вважаю за краще (не виходячи з правильної заборони) одиничні тести не використовувати змінні учасників, як мені подобається, щоб мої тести були (наскільки вони можуть бути) самостійними.


Чи є якась інша перевага перед використанням mock (XX.class), окрім того, щоб зробити тестовий випадок самостійним?
VinayVeluri

Не наскільки я знаю.
Майкл Ллойд Лі млк

3
Менше магії, щоб зрозуміти, щоб прочитати тест. Ви оголошуєте змінну і надаєте їй значення - без анотацій, відображення тощо
Кару

2

Невеликий приклад для JUnit 5 Юпітера, "RunWith" було видалено. Тепер вам потрібно використовувати розширення за допомогою анотації "@ExtendWith".

@ExtendWith(MockitoExtension.class)
class FooTest {

  @InjectMocks
  ClassUnderTest test = new ClassUnderTest();

  @Spy
  SomeInject bla = new SomeInject();
}

0

Інші відповіді чудові і містять більше деталей, якщо ви хочете / потребуєте.
На додаток до них, я хотів би додати TL; DR:

  1. Віддавайте перевагу використанню
    • @RunWith(MockitoJUnitRunner.class)
  2. Якщо ви не можете (оскільки ви вже використовуєте інший бігун), віддайте перевагу
    • @Rule public MockitoRule rule = MockitoJUnit.rule();
  3. Подібно до (2), але ви більше не повинні використовувати це:
    • @Before public void initMocks() { MockitoAnnotations.initMocks(this); }
  4. Якщо ви хочете використовувати макет лише в одному з тестів і не хочете виставляти його іншим тестам у тому ж тестовому класі, використовуйте
    • X x = mock(X.class)

(1) і (2) і (3) взаємно виключаються.
(4) можна використовувати в поєднанні з іншими.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.