Существует много способов инициализации макетного объекта с помощью 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);
Я использую это, потому что я нахожу его немного более описательным, и я предпочитаю (не из правого запрета) модульные тесты не использовать переменные-члены, поскольку мне нравятся мои тесты (насколько они могут быть) автономно.