Quelle est la différence entre @Mock
et @InjectMocks
dans le framework Mockito?
Quelle est la différence entre @Mock
et @InjectMocks
dans le framework Mockito?
Réponses:
@Mock
crée une maquette. @InjectMocks
crée une instance de la classe et injecte les simulations qui sont créées avec les annotations @Mock
(ou @Spy
) dans cette instance.
Notez que vous devez utiliser @RunWith(MockitoJUnitRunner.class)
ou Mockito.initMocks(this)
pour initialiser ces mocks et les injecter.
@RunWith(MockitoJUnitRunner.class)
public class SomeManagerTest {
@InjectMocks
private SomeManager someManager;
@Mock
private SomeDependency someDependency; // this will be injected into someManager
//tests...
}
Il s'agit d'un exemple de code expliquant comment @Mock
et @InjectMocks
fonctionne.
Disons que nous avons Game
et Player
classe.
class Game {
private Player player;
public Game(Player player) {
this.player = player;
}
public String attack() {
return "Player attack with: " + player.getWeapon();
}
}
class Player {
private String weapon;
public Player(String weapon) {
this.weapon = weapon;
}
String getWeapon() {
return weapon;
}
}
Comme vous le voyez, la Game
classe a besoinPlayer
effectuer un attack
.
@RunWith(MockitoJUnitRunner.class)
class GameTest {
@Mock
Player player;
@InjectMocks
Game game;
@Test
public void attackWithSwordTest() throws Exception {
Mockito.when(player.getWeapon()).thenReturn("Sword");
assertEquals("Player attack with: Sword", game.attack());
}
}
Mockito se moquera d'une classe Player et son comportement en utilisant when
etthenReturn
méthode. Enfin, l'utilisation de @InjectMocks
Mockito mettra cela Player
en place Game
.
Notez que vous n'avez même pas besoin de créer un new Game
objet. Mockito l'injectera pour vous.
// you don't have to do this
Game game = new Game(player);
Nous obtiendrons également le même comportement en utilisant @Spy
annotation. Même si le nom de l'attribut est différent.
@RunWith(MockitoJUnitRunner.class)
public class GameTest {
@Mock Player player;
@Spy List<String> enemies = new ArrayList<>();
@InjectMocks Game game;
@Test public void attackWithSwordTest() throws Exception {
Mockito.when(player.getWeapon()).thenReturn("Sword");
enemies.add("Dragon");
enemies.add("Orc");
assertEquals(2, game.numberOfEnemies());
assertEquals("Player attack with: Sword", game.attack());
}
}
class Game {
private Player player;
private List<String> opponents;
public Game(Player player, List<String> opponents) {
this.player = player;
this.opponents = opponents;
}
public int numberOfEnemies() {
return opponents.size();
}
// ...
C'est parce que Mockito vérifiera la Type Signature
classe de jeu, qui est Player
et List<String>
.
Dans votre classe de test, la classe testée doit être annotée avec @InjectMocks
. Cela indique à Mockito dans quelle classe injecter les mocks:
@InjectMocks
private SomeManager someManager;
À partir de là, nous pouvons spécifier quelles méthodes ou objets spécifiques à l'intérieur de la classe, dans ce cas SomeManager
, seront remplacés par des mocks:
@Mock
private SomeDependency someDependency;
Dans cet exemple, l' SomeDependency
intérieur de la SomeManager
classe sera moqué.
@Mock
l'annotation se moque de l'objet concerné.
@InjectMocks
l'annotation permet d'injecter dans l'objet sous-jacent les différents (et pertinents) mocks créés par @Mock
.
Les deux sont complémentaires.
@InjectMocks
pour construire cette classe et l'espionner aussi.
Par exemple
@Mock
StudentDao studentDao;
@InjectMocks
StudentService service;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
Ici, nous avons besoin de la classe DAO pour la classe de service. Donc, nous nous en moquons et l'injectons dans l'instance de classe de service. De même, dans le framework Spring, tous les beans @Autowired peuvent être moqués par @Mock dans jUnits et injectés dans votre bean via @InjectMocks.
MockitoAnnotations.initMocks(this)
La méthode initialise ces simulations et les injecte pour chaque méthode de test, elle doit donc être appelée dans la setUp()
méthode.
Un "framework de simulation", sur lequel Mockito est basé, est un framework qui vous donne la possibilité de créer des objets de simulation (en termes anciens, ces objets pourraient être appelés shunts, car ils fonctionnent comme des shunts pour la fonctionnalité dépendante) En d'autres termes, une simulation est utilisé pour imiter l'objet réel dont dépend votre code, vous créez un objet proxy avec le framework de simulation. En utilisant des objets fictifs dans vos tests, vous passez essentiellement des tests unitaires normaux aux tests d'intégration
Mockito est un framework de test open source pour Java publié sous la licence MIT, c'est un "framework de simulation", qui vous permet d'écrire de beaux tests avec une API propre et simple. Il existe de nombreux cadres de simulation différents dans l'espace Java, mais il existe essentiellement deux types principaux de cadres d'objets fictifs, ceux qui sont implémentés via un proxy et ceux qui sont implémentés via le remappage de classe.
Les frameworks d'injection de dépendances comme Spring vous permettent d'injecter vos objets proxy sans modifier aucun code, l'objet simulé attend qu'une certaine méthode soit appelée et il retournera un résultat attendu.
L' @InjectMocks
annotation tente d'instancier l'instance de l'objet de test et injecte des champs annotés avec @Mock
ou @Spy
dans des champs privés de l'objet de test.
MockitoAnnotations.initMocks(this)
, réinitialise l'objet de test et réinitialise les mocks, alors n'oubliez pas de l'avoir à votre @Before
/ @BeforeMethod
annotation.
Un avantage que vous obtenez avec l'approche mentionnée par @Tom est que vous n'avez pas besoin de créer de constructeurs dans SomeManager, et donc de limiter les clients pour l'instancier.
@RunWith(MockitoJUnitRunner.class)
public class SomeManagerTest {
@InjectMocks
private SomeManager someManager;
@Mock
private SomeDependency someDependency; // this will be injected into someManager
//You don't need to instantiate the SomeManager with default contructor at all
//SomeManager someManager = new SomeManager();
//Or SomeManager someManager = new SomeManager(someDependency);
//tests...
}
Que ce soit une bonne pratique ou non dépend de la conception de votre application.
Beaucoup de gens ont donné une bonne explication ici au sujet @Mock
vs @InjectMocks
. J'aime ça, mais je pense que nos tests et notre application doivent être écrits de telle manière que nous ne devrions pas avoir besoin d'utiliser@InjectMocks
.
Référence pour une lecture plus approfondie avec des exemples: https://tedvinke.wordpress.com/2014/02/13/mockito-why-you-should-not-use-injectmocks-annotation-to-autowire-fields/
@Mock
est utilisé pour déclarer / simuler les références des beans dépendants, tandis que @InjectMocks
est utilisé pour simuler le bean pour lequel le test est en cours de création.
Par exemple:
public class A{
public class B b;
public void doSomething(){
}
}
test pour la classe A
:
public class TestClassA{
@Mocks
public class B b;
@InjectMocks
public class A a;
@Test
public testDoSomething(){
}
}
L'annotation @InjectMocks peut être utilisée pour injecter automatiquement des champs fictifs dans un objet de test.
Dans l'exemple ci-dessous, @InjectMocks a utilisé pour injecter le mock dataMap dans la dataLibrary.
@Mock
Map<String, String> dataMap ;
@InjectMocks
DataLibrary dataLibrary = new DataLibrary();
@Test
public void whenUseInjectMocksAnnotation_() {
Mockito.when(dataMap .get("aData")).thenReturn("aMeaning");
assertEquals("aMeaning", dataLibrary .getMeaning("aData"));
}
Notez que ces éléments @InjectMocks
sont sur le point d'être obsolètes
obsolète @InjectMocks et calendrier de suppression dans Mockito 3/4
et vous pouvez suivre la réponse et le lien @avp sur:
Pourquoi ne pas utiliser d'annotation InjectMocks pour les champs de câblage automatique
Bien que les réponses ci-dessus aient couvert, j'ai juste essayé d'ajouter des détails minutieux que je vois manquants. La raison derrière eux (Le pourquoi).
Illustration:
Sample.java
---------------
public class Sample{
DependencyOne dependencyOne;
DependencyTwo dependencyTwo;
public SampleResponse methodOfSample(){
dependencyOne.methodOne();
dependencyTwo.methodTwo();
...
return sampleResponse;
}
}
SampleTest.java
-----------------------
@RunWith(PowerMockRunner.class)
@PrepareForTest({ClassA.class})
public class SampleTest{
@InjectMocks
Sample sample;
@Mock
DependencyOne dependencyOne;
@Mock
DependencyTwo dependencyTwo;
@Before
public void init() {
MockitoAnnotations.initMocks(this);
}
public void sampleMethod1_Test(){
//Arrange the dependencies
DependencyResponse dependencyOneResponse = Mock(sampleResponse.class);
Mockito.doReturn(dependencyOneResponse).when(dependencyOne).methodOne();
DependencyResponse dependencyTwoResponse = Mock(sampleResponse.class);
Mockito.doReturn(dependencyOneResponse).when(dependencyTwo).methodTwo();
//call the method to be tested
SampleResponse sampleResponse = sample.methodOfSample()
//Assert
<assert the SampleResponse here>
}
}