Pour l'initialisation fictive , l'utilisation du runner ou du MockitoAnnotations.initMocks
sont 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 ( SpringJUnit4ClassRunner
par 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' @Mock
annotation 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 ArticleManager
est instancié). Pas de code passe-partout.
Inconvénients: le test n'est pas autonome, moins de problèmes de code