W przypadku inicjalizacji mocków użycie runner lub the MockitoAnnotations.initMocks
jest 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 ( SpringJUnit4ClassRunner
na 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 @Mock
adnotacji 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 ArticleManager
tworzony jest mój ). Brak kodu standardowego.
Wady: test nie jest samodzielny, mniej bólu związanego z kodem