W przypadku inicjalizacji mocków użycie runner lub the MockitoAnnotations.initMocksjest rozwiązaniami ściśle równoważnymi. Z 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.
Pierwszego rozwiązania (z MockitoAnnotations.initMocks) można użyć, gdy skonfigurowałeś już określonego runnera ( SpringJUnit4ClassRunnerna przykład) w swoim przypadku testowym.
Drugie rozwiązanie (z MockitoJUnitRunner) jest bardziej klasyczne i moje ulubione. Kod jest prostszy. Korzystanie z runnera zapewnia ogromną zaletę automatycznej walidacji użycia frameworka (opisanej przez @David Wallace w tej odpowiedzi ).
Oba rozwiązania pozwalają na dzielenie się fałszywkami (i szpiegami) między metodami testowymi. W połączeniu z @InjectMocks, pozwalają na bardzo szybkie pisanie testów jednostkowych. Zredukowano standardowy kod szyfrujący, testy są łatwiejsze do odczytania. Na przykład:
@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));
}
}
Zalety: kod jest minimalny
Wady: Czarna magia. IMO to głównie zasługa adnotacji @InjectMocks. Dzięki tej adnotacji „tracisz ból kodu” (zobacz świetne komentarze @Brice )
Trzecie rozwiązanie polega na stworzeniu makiety na każdej metodzie testowej. Pozwala, jak wyjaśnił @mlk w swojej odpowiedzi, na „ samodzielny test ”.
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));
}
}
Plusy: Wyraźnie pokazujesz, jak działa twój interfejs API (BDD ...)
Wady: jest więcej standardowych kodów. (Kreacja pozorów)
Moja rekomendacja to kompromis. Użyj @Mockadnotacji z @RunWith(MockitoJUnitRunner.class), ale nie używaj @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));
}
}
Zalety: jasno pokazujesz, jak działa twój interfejs API (jak ArticleManagertworzony jest mój ). Brak kodu standardowego.
Wady: test nie jest samodzielny, mniej bólu związanego z kodem