163 votes

Quelle est la différence entre se moquer (mocking) et espionner (spying) lors de l'utilisation de Mockito?

Quel serait un cas d'utilisation pour l'utilisation d'un espion Mockito ?

Il me semble que chaque cas d'utilisation d'un espion peut être géré avec un mock, en utilisant callRealMethod.

Une différence que je peux voir est que si vous voulez que la plupart des appels de méthode soient réels, il vous économise quelques lignes de code d'utiliser un mock par rapport à un spy. Est-ce le cas ou est-ce que je rate quelque chose de plus important ?

158voto

Saurabh Patil Points 178

Différence entre un Spy et un Mock

Lorsque Mockito crée un mock - il le fait à partir de la classe d'un type, et non à partir d'une instance réelle. Le mock crée simplement une coquille vide de la classe, entièrement instrumentée pour suivre les interactions avec elle. D'un autre côté, le spy enveloppera une instance existante. Il se comportera toujours de la même manière que l'instance normale - la seule différence est qu'il sera également instrumenté pour suivre toutes les interactions avec lui.

Dans l'exemple suivant - nous créons un mock de la classe ArrayList :

@Test
public void whenCreateMock_thenCreated() {
    List mockedList = Mockito.mock(ArrayList.class);

    mockedList.add("one");
    Mockito.verify(mockedList).add("one");

    assertEquals(0, mockedList.size());
}

Comme vous pouvez le voir - ajouter un élément à la liste mockée ne fait rien en réalité - cela appelle simplement la méthode sans aucun autre effet secondaire. Un spy, en revanche, se comportera différemment - il appellera effectivement l'implémentation réelle de la méthode add et ajoutera l'élément à la liste sous-jacente :

@Test
public void whenCreateSpy_thenCreate() {
    List spyList = Mockito.spy(new ArrayList());
    spyList.add("one");
    Mockito.verify(spyList).add("one");

    assertEquals(1, spyList.size());
}

Ici, nous pouvons certainement dire que la méthode interne réelle de l'objet a été appelée car lorsque vous appelez la méthode size(), vous obtenez la taille comme 1, mais cette méthode size() n'est pas mockée ! D'où vient alors ce 1 ? La vraie méthode interne size() est appelée car size() n'est pas mockée (ou stubbée) et donc nous pouvons dire que l'entrée a été ajoutée à l'objet réel.

Source: http://www.baeldung.com/mockito-spy + notes personnelles.

113voto

JB Nizet Points 250258

La réponse se trouve dans la documentation:

Les mocks partiels réels (Depuis 1.8.0)

Enfin, après de nombreux débats internes et discussions sur la liste de diffusion, le support des mocks partiels a été ajouté à Mockito. Auparavant, nous considérions les mocks partiels comme des mauvaises pratiques. Cependant, nous avons trouvé un cas d'utilisation légitime pour les mocks partiels.

Avant la version 1.8, spy() ne produisait pas de vrais mocks partiels et cela était déroutant pour certains utilisateurs. En savoir plus sur le spying : ici ou dans la javadoc pour la méthode spy(Object).

callRealMethod() a été introduit après spy(), mais spy() est bien resté là, bien sûr, pour assurer la compatibilité ascendante.

Autrement dit, vous avez raison : toutes les méthodes d'un spy sont réelles sauf si elles sont stubbées. Toutes les méthodes d'un mock sont stubbées sauf si callRealMethod() est appelé. En général, je préfère utiliser callRealMethod(), car cela ne m'oblige pas à utiliser l'idiome doXxx().when() au lieu du traditionnel when().thenXxx()

2 votes

Le problème de préférer le mock au spy dans ces cas, c'est lorsque la classe utilise un membre qui n'est pas injecté (mais initialisé localement), et qui est ensuite utilisé par la méthode "réelle"; dans le mock, le membre sera initialisé à sa valeur Java par défaut, ce qui pourrait causer un comportement incorrect ou même une NullPointerException. La manière de contourner cela est d'ajouter une méthode "init" et ensuite de l'appeler "vraiment", mais cela me semble un peu exagéré.

1 votes

À partir du document : "les espions doivent être utilisés avec précaution et occasionnellement, par exemple lors de la manipulation de code hérité." L'espace de test unitaire souffre de trop de façons de faire la même chose.

41voto

user2412398 Points 21

Si vous avez un objet avec 8 méthodes et que vous avez un test dans lequel vous voulez appeler 7 méthodes réelles et stubber une méthode, vous avez deux options :

  1. En utilisant un mock, vous devriez le configurer en invoquant 7 appels à callRealMethod et stubber une méthode
  2. En utilisant un spy, vous devriez le configurer en stubbant une méthode

La [documentation officielle](http://site.mockito.org/mockito/docs/current/org/mockito/Mockito.html#doCallRealMethod()) sur doCallRealMethod recommande d'utiliser un spy pour les mocks partiels.

Voir aussi la javadoc spy(Object) pour en savoir plus sur les mocks partiels. Mockito.spy() est une façon recommandée de créer des mocks partiels. La raison en est qu'elle garantit que les méthodes réelles sont appelées sur un objet correctement construit car vous êtes responsable de la construction de l'objet passé à la méthode spy().

11voto

Les espions peuvent être utiles lorsque vous voulez créer des tests unitaires pour du code hérité.

J'ai créé un exemple exécutable ici https://www.surasint.com/mockito-with-spy/, j'en ai copié une partie ici.

Si vous avez quelque chose comme ce code:

public void transfer(  DepositMoneyService depositMoneyService, WithdrawMoneyService withdrawMoneyService, 
             double amount, String fromAccount, String toAccount){
    withdrawMoneyService.withdraw(fromAccount,amount);
    depositMoneyService.deposit(toAccount,amount);
}

Vous n'avez peut-être pas besoin d'un espion car vous pouvez simplement simuler DepositMoneyService et WithdrawMoneyService.

Mais avec certains codes hérités, la dépendance est dans le code comme ceci:

    public void transfer(String fromAccount, String toAccount, double amount){

        this.depositeMoneyService = new DepositMoneyService();
        this.withdrawMoneyService = new WithdrawMoneyService();

        withdrawMoneyService.withdraw(fromAccount,amount);
        depositeMoneyService.deposit(toAccount,amount);
    }

Oui, vous pouvez le changer pour le premier code mais alors l'API est modifiée. Si cette méthode est utilisée en plusieurs endroits, vous devez tous les changer.

L'alternative est d'extraire la dépendance comme ceci:

    public void transfer(String fromAccount, String toAccount, double amount){
        this.depositeMoneyService = proxyDepositMoneyServiceCreator();
        this.withdrawMoneyService = proxyWithdrawMoneyServiceCreator();

        withdrawMoneyService.withdraw(fromAccount,amount);
        depositeMoneyService.deposit(toAccount,amount);
    }
    DepositMoneyService proxyDepositMoneyServiceCreator() {
        return new DepositMoneyService();
    }

    WithdrawMoneyService proxyWithdrawMoneyServiceCreator() {
        return new WithdrawMoneyService();
    }

Ensuite, vous pouvez utiliser l'espion pour injecter la dépendance comme ceci:

DepositMoneyService mockDepositMoneyService = mock(DepositMoneyService.class);
        WithdrawMoneyService mockWithdrawMoneyService = mock(WithdrawMoneyService.class);

    TransferMoneyService target = spy(new TransferMoneyService());

    doReturn(mockDepositMoneyService)
            .when(target).proxyDepositMoneyServiceCreator();

    doReturn(mockWithdrawMoneyService)
            .when(target).proxyWithdrawMoneyServiceCreator();

Plus de détails dans le lien ci-dessus.

7voto

yoAlex5 Points 2350

Mock vs Spy

[Types de doubles de test]

Mock est un objet double brut. Cet objet a les mêmes signatures de méthodes mais sa réalisation est vide et renvoie une valeur par défaut - 0 et null

Spy est un objet double cloné. Le nouvel objet est cloné sur la base d'un objet réel mais vous avez la possibilité de le mocker

class A {
    String foo1() {
        foo2();
        return "RealString_1";
    }

    String foo2() {
        return "RealString_2";
    }

    void foo3() { foo4(); }

    void foo4() { }
}

@Test
public void testMockA() {
    //given
    A mockA = Mockito.mock(A.class);
    Mockito.when(mockA.foo1()).thenReturn("ChaineMockee");

    //when
    String result1 = mockA.foo1();
    String result2 = mockA.foo2();

    //then
    assertEquals("ChaineMockee", result1);
    assertEquals(null, result2);

    //Cas 2
    //when
    mockA.foo3();

    //then
    verify(mockA).foo3();
    verify(mockA, never()).foo4();
}

@Test
public void testSpyA() {
    //given
    A spyA = Mockito.spy(new A());

    Mockito.when(spyA.foo1()).thenReturn("ChaineMockee");

    //when
    String result1 = spyA.foo1();
    String result2 = spyA.foo2();

    //then
    assertEquals("ChaineMockee", result1);
    assertEquals("RealString_2", result2);

    //Cas 2
    //when
    spyA.foo3();

    //then
    verify(spyA).foo3();
    verify(spyA).foo4();
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X