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

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

1.

 public class SampleBaseTestCase {

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

2.

@RunWith(MockitoJUnitRunner.class)

[EDIT] 3.

mock(XXX.class);

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

Ответ 1

Для инициализации mocks использование runner или 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) можно использовать, если вы уже настроили конкретный бегун (например, t23 > ) в тестовом случае.

Второе решение (с MockitoJUnitRunner) является более классическим и моим любимым. Код проще. Использование бегуна предоставляет большое преимущество автоматическая проверка использования каркаса (описывается @Дэвид Уоллес в этот ответ).

Оба решения позволяют делиться макетами (и шпионами) между методами тестирования. В сочетании с @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). Нет кода шаблона.

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

Ответ 2

Теперь (начиная с v1.10.7) существует четвертый способ создания экземпляров mocks, который использует правило 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 test runner (поэтому его загрузчик классов может предоставлять Java-замены для обычных классов Android). Это делает его более гибким для использования в последних версиях JUnit и Mockito.

Вкратце:

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

См. также: Как работает JUnit @Rule?

Ответ 3

Существует четкий способ сделать это.

  • Если это Unit Test, вы можете сделать это:

    @RunWith(MockitoJUnitRunner.class)
    public class MyUnitTest {
    
        @Mock
        private MyFirstMock myFirstMock;
    
        @Mock
        private MySecondMock mySecondMock;
    
        @Spy
        private MySpiedClass mySpiedClass = new MySpiedClass();
    
        // It 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). Просто продемонстрируйте, что вы можете инициализировать макеты с разными 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 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() {
        }
    }
    

Ответ 4

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

XXX mockedXxx = mock(XXX.class);

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