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
MockitoJUnitRunner
. Pour plus d'informations sur les différences, consultez la question sur stackoverflow.com/questions/10806345/… et ma réponse.Collaborator collab = mock(Collaborator.class)
, à mon avis, cette façon est certainement une approche valable. Bien que cela puisse avoir tendance à être verbeux, vous pouvez gagner en compréhensibilité et en refactorabilité des tests. Les deux méthodes ont leurs avantages et leurs inconvénients, je n'ai pas encore décidé quelle approche est la meilleure. Amyway, il est toujours possible d'écrire de la merde, et dépend probablement du contexte et du codeur.Il existe maintenant (à partir de la v1.10.7) une quatrième façon d'instancier des simulations, qui utilise une règle JUnit4 appelée MockitoRule .
JUnit recherche les sous-classes de TestRule annotées avec @Rule et les utilise pour encapsuler les instructions de test fournies par le Runner . Le résultat est que vous pouvez extraire les méthodes @Before, @After, et même essayer ... de capturer les wrappers dans les règles. Vous pouvez même interagir avec ceux-ci à partir de votre test, comme le fait ExpectedException .
MockitoRule se comporte presque exactement comme MockitoJUnitRunner , sauf que vous pouvez utiliser n'importe quel autre runner, tel que Parameterized (qui permet à vos constructeurs de test de prendre des arguments afin que vos tests puissent être exécutés plusieurs fois), ou le test runner de Robolectric (ainsi son classloader peut fournir des remplacements Java pour les classes natives Android). Cela le rend strictement plus flexible à utiliser dans les versions récentes de JUnit et Mockito.
En résumé:
Mockito.mock()
: Appel direct sans prise en charge des annotations ni validation de l'utilisation.MockitoAnnotations.initMocks(this)
: Prise en charge des annotations, pas de validation d'utilisation.MockitoJUnitRunner
: Prise en charge des annotations et validation de l'utilisation, mais vous devez utiliser ce runner.MockitoRule
: Prise en charge des annotations et validation de l'utilisation avec n'importe quel runner JUnit.Voir aussi: Comment fonctionne JUnit @Rule?
la source
@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
Il existe une bonne façon de faire cela.
S'il s'agit d'un test unitaire, vous pouvez le faire:
EDIT: S'il s'agit d'un test d'intégration, vous pouvez le faire (ce n'est pas prévu pour être utilisé de cette façon avec Spring. Montrez simplement que vous pouvez initialiser des simulations avec différents Runners):
la source
MockitoAnnotations et le coureur ont été bien discutés ci-dessus, donc je vais ajouter ma tuppence pour les mal-aimés:
J'utilise ceci parce que je le trouve un peu plus descriptif et que je préfère (pas à droite interdire) les tests unitaires à ne pas utiliser de variables membres car j'aime que mes tests soient (autant qu'ils peuvent être) autonomes.
la source
Un petit exemple pour JUnit 5 Jupiter, le "RunWith" a été supprimé, vous devez maintenant utiliser les extensions en utilisant l'annotation "@ExtendWith".
la source
Les autres réponses sont excellentes et contiennent plus de détails si vous les voulez / en avez besoin.
En plus de ceux-ci, je voudrais ajouter un TL; DR:
@RunWith(MockitoJUnitRunner.class)
@Rule public MockitoRule rule = MockitoJUnit.rule();
@Before public void initMocks() { MockitoAnnotations.initMocks(this); }
X x = mock(X.class)
(1) et (2) et (3) s'excluent mutuellement.
(4) peut être utilisé en combinaison avec les autres.
la source