Pour l'initialisation fictive , l'utilisation du runner ou du MockitoAnnotations.initMockssont des solutions strictement équivalentes. Depuis le javadoc du 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.
La première solution (avec le MockitoAnnotations.initMocks) peut être utilisée lorsque vous avez déjà configuré un runner spécifique ( SpringJUnit4ClassRunnerpar exemple) sur votre cas de test.
La deuxième solution (avec le MockitoJUnitRunner) est la plus classique et ma préférée. Le code est plus simple. L'utilisation d'un runner offre le grand avantage de la validation automatique de l'utilisation du framework (décrite par @David Wallace dans cette réponse ).
Les deux solutions permettent de partager les simulacres (et les espions) entre les méthodes de test. Couplés au @InjectMocks, ils permettent d'écrire des tests unitaires très rapidement. Le code de simulation standard est réduit, les tests sont plus faciles à lire. Par exemple:
@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));
}
}
Avantages: le code est minimal
Inconvénients: magie noire. IMO, cela est principalement dû à l'annotation @InjectMocks. Avec cette annotation "vous perdez la douleur du code" (voir les bons commentaires de @Brice )
La troisième solution consiste à créer votre maquette sur chaque méthode de test. Il permet comme expliqué par @mlk dans sa réponse d'avoir un " test autonome ".
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));
}
}
Avantages: Vous démontrez clairement comment fonctionne votre API (BDD ...)
Inconvénients: il y a plus de code passe-partout. (La création de mocks)
Ma recommandation est un compromis. Utilisez l' @Mockannotation avec @RunWith(MockitoJUnitRunner.class), mais n'utilisez pas @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));
}
}
Avantages: Vous démontrez clairement comment fonctionne votre API (Comment my ArticleManagerest instancié). Pas de code passe-partout.
Inconvénients: le test n'est pas autonome, moins de problèmes de code