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

137

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

Il me semble que chaque cas d'utilisation d'espionnage peut être traité avec un simulacre, 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, cela économise quelques lignes de code pour utiliser une simulation par rapport à un espion. Est-ce cela ou est-ce que je rate la vue d'ensemble?

Victor Grazi
la source

Réponses:

100

La réponse est dans la documentation :

Vraies simulations partielles (depuis 1.8.0)

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

Avant la version 1.8, spy () ne produisait pas de véritables simulations partielles et c'était déroutant pour certains utilisateurs. En savoir plus sur l'espionnage: ici ou en javadoc pour la méthode spy (Object).

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

Sinon, vous avez raison: toutes les méthodes d'un espion sont réelles à moins d'être écrasées. Toutes les méthodes d'un simulacre sont stubbed sauf si callRealMethod()est appelé. En général, je préférerais utiliser callRealMethod(), car cela ne m'oblige pas à utiliser l' doXxx().when()idiome au lieu du traditionnelwhen().thenXxx()

JB Nizet
la source
Le problème de préférer le simulacre à l'espion dans ces cas, c'est quand la classe utilise un membre qui n'est pas injecté dedans (mais initialisé localement), et est plus tard utilisé par la méthode "réelle"; dans le simulacre, le membre sera initialisé à sa valeur Java par défaut, ce qui pourrait entraîner un comportement incorrect ou même une exception NullPointerException. La façon de réussir est d'ajouter une méthode "init" puis de l'appeler "vraiment", mais cela me semble un peu exagéré.
Eyal Roth
Extrait du document: "Les espions doivent être utilisés avec précaution et occasionnellement, par exemple lors de l'utilisation de code hérité." L'espace de test unitaire souffre de trop de façons de faire la même chose.
gdbj
89

Différence entre un espion et un simulacre

Lorsque Mockito crée une maquette, il le fait à partir de la classe d'un type, et non d'une instance réelle. Le simulacre crée simplement une instance de shell simple de la classe, entièrement instrumentée pour suivre les interactions avec elle. D'autre part, l'espion encapsulera une instance existante. Elle se comportera toujours de la même manière que l'instance normale - la seule différence est qu'elle sera également instrumentée pour suivre toutes les interactions avec elle.

Dans l'exemple suivant, nous créons une maquette 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 - l'ajout d'un élément dans la liste simulée n'ajoute en fait rien - il appelle simplement la méthode sans autre effet secondaire. Un espion, par contre, se comportera différemment - il appellera en fait 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 sûrement dire que la vraie méthode interne de l'objet a été appelée car lorsque vous appelez la méthode size (), vous obtenez la taille 1, mais cette méthode size () n'est pas moquée! Alors d'où viens-je? La méthode interne real size () est appelée car size () n'est pas simulée (ou stubbed) et nous pouvons donc dire que l'entrée a été ajoutée à l'objet réel.

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

Saurabh Patil
la source
1
Ne voulez-vous pas dire que size () renvoie 1?
noir du
Dans le premier exemple, pourquoi mockedList.size()retourne-t-il 0si cette méthode n'a pas non plus été supprimée? Est-ce juste une valeur par défaut étant donné le type de retour de la méthode?
mike
@mike: mockedList.size()renvoie une intvaleur par défaut de int0 en Java. Si vous essayez d'exécuter assertEquals(0, mockedList.size());après mockedList.clear();, le résultat reste le même.
realPK
2
Cette réponse est bien et simplement écrite et m'a aidé à enfin comprendre la différence entre mock et espion. Joli.
PesaLe
38

S'il y a un objet avec 8 méthodes et que vous avez un test où vous voulez appeler 7 méthodes réelles et stub une méthode, vous avez deux options:

  1. En utilisant un simulacre, vous devrez le configurer en appelant 7 callRealMethod et en stub une méthode
  2. En utilisant un, spyvous devez le configurer en stubbing une méthode

La documentation officielle sur doCallRealMethodrecommande l'utilisation d'un espion pour les simulations partielles.

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

user2412398
la source
5

Spy peut être utile lorsque vous souhaitez 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 copie 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'espionnage, car vous pouvez simplement vous moquer de DepositMoneyService et WithdrawMoneyService.

Mais avec du code hérité, 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 passer au premier code, mais l'API est ensuite modifiée. Si cette méthode est utilisée par de nombreux endroits, vous devez tous les changer.

L'alternative est que vous pouvez 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.

Surasin Tancharoen
la source
0

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

Spyest un double objet cloné. Le nouvel objet est cloné sur la base d'un objet réel mais vous avez la possibilité de vous en moquer

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("MockedString");

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

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

    //Case 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("MockedString");

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

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

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

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

[Tester les types doubles]

yoAlex5
la source