Mockito - @Spy contre @Mock

99

Mockito - Je comprends qu'un espion appelle les méthodes réelles sur un objet, tandis qu'un simulacre appelle des méthodes sur l'objet double. Les espions doivent également être évités à moins qu'il n'y ait une odeur de code. Cependant, comment fonctionnent les espions et quand dois-je les utiliser? En quoi sont-ils différents des simulacres?

Abhinav
la source
2
doublon possible de mockito mock vs spy
rds
Reproduction

Réponses:

88

Techniquement parlant, les «mocks» et les «espions» sont un type spécial de «tests doubles».

Mockito rend malheureusement la distinction bizarre.

Une maquette dans mockito est une maquette normale dans d'autres frameworks moqueurs (vous permet de stuber les appels , c'est-à-dire de renvoyer des valeurs spécifiques hors des appels de méthode).

Un espion dans mockito est une simulation partielle dans d'autres frameworks moqueurs (une partie de l'objet sera moquée et une partie utilisera des invocations de méthodes réelles).

Crazyjavahacking
la source
41

Les deux peuvent être utilisés pour simuler des méthodes ou des champs. La différence est que dans le simulacre, vous créez un objet simulé ou faux complet tandis que dans l'espionnage, il y a l'objet réel et vous ne faites qu'espionner ou en stuber des méthodes spécifiques.

Dans les objets espions, bien sûr, puisqu'il s'agit d'une méthode réelle, lorsque vous ne stubbing pas la méthode, elle appellera le comportement réel de la méthode. Si vous souhaitez modifier et vous moquer de la méthode, vous devez la stuber.

Considérez l'exemple ci-dessous à titre de comparaison.

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.runners.MockitoJUnitRunner;
 
import java.util.ArrayList;
import java.util.List;
 
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
 
@RunWith(MockitoJUnitRunner.class)
public class MockSpy {
 
    @Mock
    private List<String> mockList;
 
    @Spy
    private List<String> spyList = new ArrayList();
 
    @Test
    public void testMockList() {
        //by default, calling the methods of mock object will do nothing
        mockList.add("test");

        Mockito.verify(mockList).add("test");
        assertEquals(0, mockList.size());
        assertNull(mockList.get(0));
    }
 
    @Test
    public void testSpyList() {
        //spy object will call the real method when not stub
        spyList.add("test");

        Mockito.verify(spyList).add("test");
        assertEquals(1, spyList.size());
        assertEquals("test", spyList.get(0));
    }
 
    @Test
    public void testMockWithStub() {
        //try stubbing a method
        String expected = "Mock 100";
        when(mockList.get(100)).thenReturn(expected);
 
        assertEquals(expected, mockList.get(100));
    }
 
    @Test
    public void testSpyWithStub() {
        //stubbing a spy method will result the same as the mock object
        String expected = "Spy 100";
        //take note of using doReturn instead of when
        doReturn(expected).when(spyList).get(100);
 
        assertEquals(expected, spyList.get(100));
    }
}

Quand devriez-vous utiliser la simulation ou l'espionnage? Si vous voulez être en sécurité et éviter d'appeler des services externes et que vous voulez simplement tester la logique à l'intérieur de l'unité, utilisez mock. Si vous souhaitez appeler un service externe et effectuer un appel de dépendance réelle, ou simplement dire que vous souhaitez exécuter le programme tel quel et simplement des méthodes spécifiques au stub, utilisez spy. Voilà donc la différence entre espionner et simuler dans mockito.

Vu Truong
la source
Bonne réponse mais cela lancera verify () sur une erreur fictive uniquement et n'exécutera pas les tests à moins que vous n'initialisiez vos listes dans la méthode @Before setUp () comme ici mockList = mock (ArrayList.class); spyList = espion (ArrayList.class); et supprimez l'annotation simulée et espion suggérée ici. Je l'ai testé et mes tests réussissent maintenant.
The_Martian
17

TL; version DR,

Avec simulacre , il crée une instance de coque nue pour vous.

List<String> mockList = Mockito.mock(ArrayList.class);

Avec spy, vous pouvez vous moquer partiellement d'une instance existante

List<String> spyList = Mockito.spy(new ArrayList<String>());

Cas d'utilisation typique pour Spy: la classe a un constructeur paramétré, vous voulez d'abord créer l'objet.

del bao
la source
14

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
13

Le meilleur endroit pour commencer est probablement la documentation pour mockito .

D'une manière générale, la maquette mockito vous permet de créer des talons.

Vous créeriez une méthode stub si, par exemple, cette méthode effectue une opération coûteuse. Disons qu'il obtient une connexion à la base de données, récupère une valeur de la base de données et la renvoie à l'appelant. L'obtention de la connexion à la base de données peut prendre 30 secondes, ralentissant l'exécution de votre test au point où vous basculerez probablement de contexte (ou arrêterez d'exécuter le test).

Si la logique que vous testez ne se soucie pas de la connexion à la base de données, vous pouvez remplacer cette méthode par un stub qui renvoie une valeur codée en dur.

L'espion mockito vous permet de vérifier si une méthode appelle d'autres méthodes. Cela peut être très utile lorsque vous essayez de tester le code hérité.

Il est utile si vous testez une méthode qui fonctionne à travers les effets secondaires, alors vous utiliseriez un espion mockito. Cela délègue les appels à l'objet réel et vous permet de vérifier l'appel de méthode, le nombre de fois invoqué, etc.

Jaimie Whiteside
la source
7

En bref:

@Spyet @Mocksont largement utilisés dans les tests de code, mais les développeurs ne savent pas quand utiliser l'un d'eux et donc les développeurs finissent par utiliser @Mockpour être sûrs.

  • À utiliser @Mocklorsque vous souhaitez simplement tester la fonctionnalité en externe sans appeler cette méthode.
  • À utiliser @Spylorsque vous souhaitez tester la fonctionnalité en externe + en interne avec la méthode même appelée.

Voici l'exemple où j'ai pris le scénario d' Election20xx en Amérique.

Les électeurs peuvent être divisés selon VotersOfBelow21et VotersOfABove21.

Le sondage de sortie idéal dit que Trump gagnera les élections parce VotersOfBelow21que les VotersOfABove21deux voteront pour Trump en disant " Nous avons élu le président Trump "

Mais ce n'est pas le vrai scénario:

Les électeurs des deux groupes d'âge ont voté pour Trump parce qu'ils n'avaient pas d'autre choix efficace que M. Trump.

Alors, comment le tester?

public class VotersOfAbove21 {
public void weElected(String myVote){
  System.out.println("Voters of above 21 has no Choice Than Thrump in 20XX ");
}
}

public class VotersOfBelow21 {
  public void weElected(String myVote){
    System.out.println("Voters of below 21 has no Choice Than Thrump in 20XX");
  }
}

public class ElectionOfYear20XX {
  VotersOfAbove21 votersOfAbove21;
  VotersOfBelow21 votersOfBelow21;
  public boolean weElected(String WeElectedTrump){
    votersOfAbove21.weElected(WeElectedTrump);
    System.out.println("We elected President Trump ");

    votersOfBelow21.weElected(WeElectedTrump);
    System.out.println("We elected President Trump ");
    return true;
  }

}

Notez maintenant que dans les deux premières classes ci-dessus, les deux personnes du groupe d'âge disent qu'elles n'ont pas de meilleur choix que Trump. Ce qui signifie explicitement qu'ils ont voté pour Trump simplement parce qu'ils n'avaient pas le choix.

Maintenant, le ElectionOfYear20XX dit que Trump a gagné parce que les deux groupes d'âge ont voté pour lui à une écrasante majorité.

Si nous devions tester le ElectionOfYear20XXavec @Mock, alors nous ne serions peut-être pas en mesure d'obtenir la vraie raison pour laquelle Trump a gagné, nous allons simplement tester la raison externe.

Si nous testons le ElectionOfYear20XXavec @Spy, alors nous obtenons la vraie raison pour laquelle Trump a gagné avec les résultats du sondage de sortie externe, c'est-à-dire en interne + en externe.


Notre ELectionOfYear20XX_Testclasse:

@RunWith(MockitoJUnitRunner.class)
public class ELectionOfYear20XX_Test {

  @Mock
  VotersOfBelow21 votersOfBelow21;
  @Mock
  VotersOfAbove21 votersOfAbove21;
  @InjectMocks
  ElectionOfYear20XX electionOfYear20XX;
  @Test
  public void testElectionResults(){
    Assert.assertEquals(true,electionOfYear20XX.weElected("No Choice"));
  }

}

Cela devrait produire uniquement les résultats du test logique, c'est-à-dire une vérification externe:

We elected President Trump 
We elected President Trump 

Test en @Spyexterne et en interne avec un appel de méthode réel.

@RunWith(MockitoJUnitRunner.class)
public class ELectionOfYear20XX_Test {

  @Spy
  VotersOfBelow21 votersOfBelow21;
  @Spy
  VotersOfAbove21 votersOfAbove21;
  @InjectMocks
  ElectionOfYear20XX electionOfYear20XX;
  @Test
  public void testElectionResults(){
    Assert.assertEquals(true,electionOfYear20XX.weElected("No Choice"));
  }

}

Production:

Voters of above 21 has no Choice Than Thrump in 20XX 
We elected President Trump 
Voters of below 21 has no Choice Than Thrump in 20XX
We elected President Trump 
Vishwa Ratna
la source
6

J'aime la simplicité de cette recommandation:

  • Si vous voulez être en sécurité et éviter d'appeler des services externes et que vous voulez simplement tester la logique à l'intérieur de l'unité, utilisez mock .
  • Si vous voulez appeler un service externe et appeler de vraies dépendances, ou simplement dire que vous voulez exécuter le programme tel quel et juste des méthodes spécifiques de stub, utilisez spy .

Source: https://javapointers.com/tutorial/difference-between-spy-and-mock-in-mockito/

Une différence commune est:

  • Si vous souhaitez directement stuber la ou les méthodes d'une dépendance, alors Mock cette dépendance.
  • Si vous souhaitez stuber les données dans une dépendance afin que toutes ses méthodes renvoient les valeurs de test dont vous avez besoin, alors Spy cette dépendance.
leo9r
la source
Notez que Spy et Mock sont toujours destinés aux dépendances, et non au système testé.
leo9r