Différence entre @Mock et @InjectMocks

Réponses:

542

@Mockcrée une maquette. @InjectMockscré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...

}
Tom Verelst
la source
2
Réponse courte et concise. Utile aussi;)
Chaklader Asfak Arefe
Est-ce que cela fonctionne pour les dépendances transitives ou uniquement pour les membres directs?
Pierre Thibault
@PierreThibault L'injection de mocks ne fonctionne que pour les membres directs, mais vous pouvez définir une mock pour autoriser les stubs
Tom Verelst
1
je pense que c'est beaucoup plus clair que la plupart de l'article en ligne .... que les petits commentaires me sauvent le cul ...
IHC_Applroid
J'ai certains éléments qui ne peuvent pas être fournis par l'annotation @Mock comme le contexte. Comment puis-je fournir cela pour la classe principale?
Mahdi
220

Il s'agit d'un exemple de code expliquant comment @Mocket @InjectMocksfonctionne.

Disons que nous avons Game et Playerclasse.

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 Gameclasse 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 whenetthenReturn méthode. Enfin, l'utilisation de @InjectMocksMockito mettra cela Playeren 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 Signatureclasse de jeu, qui est Playeret List<String>.

aldok
la source
16
Avec cet exemple, ce devrait être la réponse acceptée.
AnnaKlein
4
Je pense que c'est aussi la meilleure réponse
Evgeniy Dorofeev
4
Je pense que c'est la meilleure réponse triple.
Harvey Dent
1
Je trouve parfois les tests avec la moquerie difficiles à comprendre et à concevoir pour une classe. Cependant, cet exemple aide beaucoup à fournir une vue d'ensemble.
Chaklader Asfak Arefe
1
Merci beaucoup :) Au point avec une meilleure explication.
Rishi
80

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' SomeDependencyintérieur de la SomeManagerclasse sera moqué.

trdngy
la source
6
cela fonctionnera-t-il si someManager a plus d'un constructeur? Et si someManager avait 5 constructeurs, comment saurait-il lequel vous souhaitez utiliser?
j2emanue
51

@Mock l'annotation se moque de l'objet concerné.

@InjectMocksl'annotation permet d'injecter dans l'objet sous-jacent les différents (et pertinents) mocks créés par @Mock.

Les deux sont complémentaires.

Mik378
la source
1
Peuvent-ils être utilisés en tandem sur le même objet?
IgorGanapolsky
1
Avez-vous un mini-exemple de votre besoin?
Mik378
J'ai une classe qui doit être espionnée (via Mockito Spy), et cette classe a un constructeur. Je pensais donc à utiliser @InjectMockspour construire cette classe et l'espionner aussi.
IgorGanapolsky
1
C'est ça que vous cherchez? stackoverflow.com/a/35969166/985949
Mik378
23
  • @Mock crée une implémentation fictive pour les classes dont vous avez besoin.
  • @InjectMock crée une instance de la classe et y injecte les mocks marqués des annotations @Mock .

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.

Ce lien a un bon tutoriel pour le framework Mockito

Sana Jahan
la source
13

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' @InjectMocksannotation tente d'instancier l'instance de l'objet de test et injecte des champs annotés avec @Mockou @Spydans 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/ @BeforeMethodannotation.

serup
la source
2
Je ne dirais pas que "En utilisant des objets fictifs dans vos tests, vous passez essentiellement des tests unitaires normaux aux tests d'intégration". Pour moi, la moquerie consiste à isoler le luminaire à tester afin d'effectuer un test unitaire. Les tests d'intégration utiliseront de véritables dépendances non fictives.
WesternGun
10

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.

tintin
la source
Et si someManager avait 3 constructeurs différents, comment saurait-il lequel utiliser?
j2emanue
Comment vérifiez-vous ensuite les choses sur someManager si elles ne sont pas moquées?
IgorGanapolsky
5

Beaucoup de gens ont donné une bonne explication ici au sujet @Mockvs @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/

avp
la source
1
Celui-ci semble être une solution à long terme.
Vinayak Dornala
4

@Mockest utilisé pour déclarer / simuler les références des beans dépendants, tandis que @InjectMocksest 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(){

   }

}
dev_2014
la source
4

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"));
    }
Lahiru Wijesekara
la source
3

Notez que ces éléments @InjectMockssont 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

user7294900
la source
3

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).

entrez la description de l'image ici


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>
    }
}

Référence

Angelin Nadar
la source