Инициализация фиктивных объектов - MockIto

122

Есть много способов инициализировать фиктивный объект с помощью MockIto. Какой из них лучший?

1.

 public class SampleBaseTestCase {

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

2.

@RunWith(MockitoJUnitRunner.class)

[РЕДАКТИРОВАТЬ] 3.

mock(XXX.class);

предложите мне, есть ли другие способы лучше, чем эти ...

VinayVeluri
источник

Ответы:

153

Для инициализации mocks использование runner или the 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));
    }
}

Плюсы: Код минимальный

Минусы: Черная магия. ИМО, это в основном из-за аннотации @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создается экземпляр my ). Нет шаблонного кода.

Минусы: тест не самодостаточен, меньше проблем с кодом.

Gontard
источник
Однако будьте осторожны, аннотации полезны, но они не защищают вас от создания плохого объектно-ориентированного дизайна (или его ухудшения). Лично, хотя я рад сокращать шаблонный код, я теряю боль кода (или PITA), который является триггером для изменения дизайна на лучший, поэтому я и моя команда обращаем внимание на объектно-ориентированный дизайн. Я считаю, что следование объектно-ориентированному дизайну с такими принципами, как SOLID-дизайн или идеи GOOS, намного важнее, чем выбор способа создания макетов.
Brice
1
(продолжение) Если вы не видите, как создается этот объект, вы не чувствуете по этому поводу боли, и будущие программисты могут плохо отреагировать на добавление новых функций. Во всяком случае , что спорно оба пути, я просто говорю , чтобы быть осторожным.
Brice
6
НЕПРАВИЛЬНО, что эти два эквивалента. НЕПРАВИЛЬНО, что более простой код - единственное преимущество использования MockitoJUnitRunner. Для получения дополнительной информации о различиях см. Вопрос на stackoverflow.com/questions/10806345/… и мой ответ на него.
Давуд ибн Карим
2
@Gontard Да, зависимости видны, но я видел, как при таком подходе код работал неправильно. Что касается использования Collaborator collab = mock(Collaborator.class), на мой взгляд, это, безусловно, правильный подход. Хотя это может быть многословным, вы можете получить понятность и возможность рефакторинга тестов. У обоих способов есть свои плюсы и минусы, я еще не решил, какой подход лучше. Эмиуэй всегда можно написать чушь, и это, вероятно, зависит от контекста и кодировщика.
Brice
1
@mlk Я полностью с вами согласен. Мой английский не очень хорош и не хватает нюансов. Я хотел настоять на слове UNIT.
gontard
30

Теперь (начиная с версии 1.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() {
        }
    }
    
  • РЕДАКТИРОВАТЬ: если это тест интеграции, вы можете сделать это (не предназначено для использования таким образом с Spring. Просто продемонстрируйте, что вы можете инициализировать макеты с разными Runners):

    @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() {
        }
    }
    
EMD
источник
1
Если MOCK также участвует в интеграционных тестах, будет ли это иметь смысл?
VinayVeluri
2
на самом деле не будет, ваше право. Я просто хотел показать возможности Mockito. Например, если вы используете RESTFuse, вы должны использовать их бегун, чтобы вы могли инициализировать макеты с помощью MockitoAnnotations.initMocks (this);
emd
8

MockitoAnnotations и раннер были хорошо обсуждены выше, поэтому я собираюсь добавить свою помощь нелюбимым:

XXX mockedXxx = mock(XXX.class);

Я использую это, потому что считаю его немного более описательным, и я предпочитаю (а не из-за правильного запрета) модульные тесты не использовать переменные-члены, поскольку мне нравится, чтобы мои тесты были (насколько это возможно) самодостаточными.

Майкл Ллойд Ли млк
источник
Есть ли другие преимущества перед использованием mock (XX.class), кроме как сделать тестовый пример самодостаточным?
VinayVeluri
Насколько мне известно, нет.
Michael Lloyd Lee mlk
3
Меньше магии, которую нужно понимать, чтобы читать тест. Вы объявляете переменную и присваиваете ей значение - без аннотаций, отражений и т. Д.
Кару
2

Небольшой пример для JUnit 5 Jupiter, «RunWith» был удален, теперь вам нужно использовать расширения, используя аннотацию «@ExtendWith».

@ExtendWith(MockitoExtension.class)
class FooTest {

  @InjectMocks
  ClassUnderTest test = new ClassUnderTest();

  @Spy
  SomeInject bla = new SomeInject();
}
fl0w
источник
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) можно использовать в сочетании с другими.

нексус
источник