creating mocks spies mockito with code examples
Tutoriel Mockito Spy and Mocks:
Dans ce Série de tutoriels Mockito , notre précédent tutoriel nous a donné un Introduction à Mockito Framework . Dans ce tutoriel, nous allons apprendre le concept de Mocks and Spies dans Mockito.
Que sont les moqueurs et les espions?
Les Mocks et les Spies sont les types de doubles de test, qui sont utiles pour écrire des tests unitaires.
Les mocks remplacent complètement la dépendance et peuvent être programmés pour renvoyer la sortie spécifiée chaque fois qu'une méthode sur le mock est appelée. Mockito fournit une implémentation par défaut pour toutes les méthodes d'un simulacre.
Ce que vous apprendrez:
- Que sont les espions?
- Créer des simulacres
- Créer des espions
- Comment injecter des dépendances simulées pour la classe / objet sous test?
- Conseils & Astuces
- Exemples de code - Spies & Mocks
- Code source
- lecture recommandée
Que sont les espions?
Les espions sont essentiellement un wrapper sur une instance réelle de la dépendance simulée. Cela signifie qu'il nécessite une nouvelle instance de l'objet ou de la dépendance, puis ajoute un wrapper de l'objet simulé dessus. Par défaut, les espions appellent les méthodes réelles de l'objet à moins d'être stubbed.
Les espions fournissent certains pouvoirs supplémentaires tels que les arguments fournis à l'appel de méthode, la vraie méthode appelée, etc.
En un mot, pour les Spies:
- L'instance réelle de l'objet est requise.
- Spies donne la flexibilité de stub certaines (ou toutes) méthodes de l'objet espionné. À ce moment-là, l'espion est essentiellement appelé ou référé à un objet partiellement moqué ou stubbed.
- Les interactions appelées sur un objet espionné peuvent être suivies pour vérification.
En général, les espions ne sont pas très fréquemment utilisés, mais peuvent être utiles pour les tests unitaires des applications héritées où les dépendances ne peuvent pas être entièrement simulées.
Pour toute la description de Mock and Spy, nous faisons référence à une classe / objet fictif appelé «DiscountCalculator» que nous voulons simuler / espionner.
Il a quelques méthodes comme indiqué ci-dessous:
calculerRéduction - Calcule le prix réduit d'un produit donné.
getDiscountLimit - Récupère la limite supérieure de remise pour le produit.
Créer des simulacres
# 1) Création de maquette avec code
Mockito donne plusieurs versions surchargées de Mockito. Mocks méthode et permet de créer des mocks pour les dépendances.
Syntaxe:
Mockito.mock(Class classToMock)
Exemple:
Supposons que le nom de la classe soit DiscountCalculator, pour créer une maquette dans le code:
DiscountCalculator mockedDiscountCalculator = Mockito.mock(DiscountCalculator.class)
Il est important de noter que Mock peut être créé à la fois pour l'interface ou pour une classe concrète.
Lorsqu'un objet est simulé, à moins d'être stubé, toutes les méthodes renvoient null par défaut .
DiscountCalculator mockDiscountCalculator = Mockito.mock(DiscountCalculator.class);
# 2) Création de maquette avec annotations
Au lieu de se moquer en utilisant la méthode statique «mock» de la bibliothèque Mockito, il fournit également un moyen abrégé de créer des simulations en utilisant l'annotation «@Mock».
Le plus grand avantage de cette approche est qu'elle est simple et permet de combiner déclaration et essentiellement initialisation. Cela rend également les tests plus lisibles et évite l'initialisation répétée des simulations lorsque la même simulation est utilisée à plusieurs endroits.
Afin d'assurer l'initialisation de Mock via cette approche, il est nécessaire que nous appelions «MockitoAnnotations.initMocks (this)» pour la classe testée. C’est le candidat idéal pour faire partie de la méthode ‘beforeEach’ de Junit, qui garantit que les simulations sont initialisées à chaque fois qu’un test est exécuté à partir de cette classe.
Syntaxe:
@Mock private transient DiscountCalculator mockedDiscountCalculator;
Créer des espions
Semblable aux Mocks, les Spies peuvent également être créés de 2 manières:
# 1) Création d'espions avec code
Mockito.spy est la méthode statique utilisée pour créer un objet / wrapper «espion» autour de l'instance d'objet réel.
Syntaxe:
test unitaire vs exemple de test d'intégration
private transient ItemService itemService = new ItemServiceImpl() private transient ItemService spiedItemService = Mockito.spy(itemService);
# 2) Création d'espions avec annotations
Semblable à Mock, les espions peuvent être créés à l'aide de l'annotation @Spy.
Pour l'initialisation de Spy également, vous devez vous assurer que MockitoAnnotations.initMocks (this) est appelé avant que le Spy ne soit utilisé dans le test réel afin d'obtenir l'initialisation de l'espion.
Syntaxe:
@Spy private transient ItemService spiedItemService = new ItemServiceImpl();
Comment injecter des dépendances simulées pour la classe / objet sous test?
Lorsque nous voulons créer un objet fictif de la classe testée avec les autres dépendances simulées, nous pouvons utiliser l'annotation @InjectMocks.
Ce que cela fait essentiellement, c'est que tous les objets marqués avec des annotations @Mock (ou @Spy) sont injectés en tant qu'injection de Contractor ou de propriété dans la classe Object, puis les interactions peuvent être vérifiées sur l'objet final Mocked.
Encore une fois, inutile de mentionner, @InjectMocks est un raccourci contre la création d'un nouvel objet de la classe et fournit des objets simulés des dépendances.
Comprenons cela avec un exemple:
Supposons qu'il existe une classe PriceCalculator, qui a DiscountCalculator et UserService comme dépendances qui sont injectées via les champs Constructor ou Property.
Ainsi, afin de créer l'implémentation Mocked pour la classe de calculateur de prix, nous pouvons utiliser 2 approches:
# 1) Créer une nouvelle instance de PriceCalculator et injecter des dépendances simulées
@Mock private transient DiscountCalculator mockedDiscountCalculator; @Mock private transient UserService userService; @Mock private transient ItemService mockedItemService; private transient PriceCalculator priceCalculator; @BeforeEach public void beforeEach() { MockitoAnnotations.initMocks(this); priceCalculator = new PriceCalculator(mockedDiscountCalculator, userService, mockedItemService); }
# 2) Créer une instance simulée de PriceCalculator et injecter des dépendances via l'annotation @InjectMocks
@Mock private transient DiscountCalculator mockedDiscountCalculator; @Mock private transient UserService userService; @Mock private transient ItemService mockedItemService; @InjectMocks private transient PriceCalculator priceCalculator; @BeforeEach public void beforeEach() { MockitoAnnotations.initMocks(this);
L'annotation InjectMocks essaie en fait d'injecter des dépendances simulées en utilisant l'une des approches ci-dessous:
- Injection basée sur le constructeur - Utilise Constructor pour la classe testée.
- Basé sur les méthodes du poseur - Quand un constructeur n'est pas là, Mockito essaie d'injecter en utilisant des setters de propriété.
- Basé sur le terrain - Lorsque les 2 ci-dessus ne sont pas disponibles, il essaie directement d'injecter via des champs.
Conseils & Astuces
# 1) Configuration de différents stubs pour différents appels de la même méthode:
Lorsqu'une méthode stubbed est appelée plusieurs fois dans la méthode testée (ou si la méthode stubbed est dans la boucle et que vous souhaitez renvoyer une sortie différente à chaque fois), vous pouvez configurer Mock pour renvoyer une réponse stubbed différente à chaque fois.
Par exemple: Supposez que vous vouliez ItemService pour renvoyer un élément différent pour 3 appels consécutifs et que vous avez des éléments déclarés dans votre méthode sous tests en tant que Item1, Item2 et Item3, vous pouvez simplement les retourner pour 3 appels consécutifs en utilisant le code ci-dessous:
@Test public void calculatePrice_withCorrectInput_returnsValidResult() { // Arrange ItemSku item1 = new ItemSku(); ItemSku item2 = new ItemSku(); ItemSku item3 = new ItemSku(); // Setup Mocks when(mockedItemService.getItemDetails(anyInt())).thenReturn(item1, item2, item3); // Assert //TODO - add assert statements }
#deux) Lancer une exception via Mock: Il s'agit d'un scénario très courant lorsque vous souhaitez tester / vérifier une dépendance / en aval lançant une exception et vérifier le comportement du système testé. Cependant, pour lever une exception par Mock, vous devrez configurer le stub en utilisant thenThrow.
@Test public void calculatePrice_withInCorrectInput_throwsException() { // Arrange ItemSku item1 = new ItemSku(); // Setup Mocks when(mockedItemService.getItemDetails(anyInt())).thenThrow(new ItemServiceException(anyString())); // Assert //TODO - add assert statements }
Pour les matchs comme anyInt () et anyString (), ne vous laissez pas intimider car ils seront traités dans les articles à venir. Mais en substance, ils vous donnent simplement la flexibilité de fournir une valeur Integer et String respectivement sans aucun argument de fonction spécifique.
Exemples de code - Spies & Mocks
Comme indiqué précédemment, les Spies et les Mocks sont le type de double test et ont leurs propres usages.
Alors que les espions sont utiles pour tester les applications héritées (et lorsque les simulations ne sont pas possibles), pour toutes les autres méthodes / classes testables bien écrites, Mocks suffit à la plupart des besoins de tests unitaires.
Pour le même exemple: Écrivons un test en utilisant Mocks pour PriceCalculator -> méthode CalculatePrice (La méthode calcule itemPrice moins les remises applicables)
La classe PriceCalculator et la méthode testée CalculatePrice se présentent comme suit:
public class PriceCalculator { public DiscountCalculator discountCalculator; public UserService userService; public ItemService itemService; public PriceCalculator(DiscountCalculator discountCalculator, UserService userService, ItemService itemService) { this.discountCalculator = discountCalculator; this.userService = userService; this.itemService = itemService; } public double calculatePrice(int itemSkuCode, int customerAccountId) { double price = 0; // get Item details ItemSku sku = itemService.getItemDetails(itemSkuCode); // get User and calculate price CustomerProfile customerProfile = userService.getUser(customerAccountId); double basePrice = sku.getPrice(); price = basePrice - (basePrice* (sku.getApplicableDiscount() + customerProfile.getExtraLoyaltyDiscountPercentage())/100); return price; } }
Maintenant, écrivons un test positif pour cette méthode.
Nous allons stub userService et item service comme mentionné ci-dessous:
- UserService renverra toujours CustomerProfile avec la valeur de fidélitéDiscountPercentage à 2.
- ItemService renverra toujours un article avec le prix de base de 100 et le rabais applicable de 5.
- Avec les valeurs ci-dessus, le expectedPrice retourné par la méthode testée est de 93 $.
Voici le code pour le test:
@Test public void calculatePrice_withCorrectInput_returnsExpectedPrice() { // Arrange ItemSku item1 = new ItemSku(); item1.setApplicableDiscount(5.00); item1.setPrice(100.00); CustomerProfile customerProfile = new CustomerProfile(); customerProfile.setExtraLoyaltyDiscountPercentage(2.00); double expectedPrice = 93.00; // Setting up stubbed responses using mocks when(mockedItemService.getItemDetails(anyInt())).thenReturn(item1); when(mockedUserService.getUser(anyInt())).thenReturn(customerProfile); // Act double actualPrice = priceCalculator.calculatePrice(123,5432); // Assert assertEquals(expectedPrice, actualPrice); }
Comme vous pouvez le voir, dans le test ci-dessus - Nous affirmons que le prix réel retourné par la méthode est égal au prix attendu, c'est-à-dire 93,00.
Maintenant, écrivons un test avec Spy.
Nous allons espionner ItemService et coderons l'implémentation ItemService de manière à ce qu'elle renvoie toujours un article avec le basePrice 200 et applicableDiscount de 10,00% (le reste de la configuration fictive reste le même) chaque fois qu'il est appelé avec skuCode de 2367.
@InjectMocks private PriceCalculator priceCalculator; @Mock private DiscountCalculator mockedDiscountCalculator; @Mock private UserService mockedUserService; @Spy private ItemService mockedItemService = new ItemServiceImpl(); @BeforeEach public void beforeEach() { MockitoAnnotations.initMocks(this); } @Test public void calculatePrice_withCorrectInputRealMethodCall_returnsExpectedPrice() { // Arrange CustomerProfile customerProfile = new CustomerProfile(); customerProfile.setExtraLoyaltyDiscountPercentage(2.00); double expectedPrice = 176.00; // Setting up stubbed responses using mocks when(mockedUserService.getUser(anyInt())).thenReturn(customerProfile); // Act double actualPrice = priceCalculator.calculatePrice(2367,5432); // Assert assertEquals(expectedPrice, actualPrice);
Voyons maintenant un Exemple d'une exception levée par ItemService car la quantité d'article disponible était de 0. Nous allons mettre en place une simulation pour lever une exception.
@InjectMocks private PriceCalculator priceCalculator; @Mock private DiscountCalculator mockedDiscountCalculator; @Mock private UserService mockedUserService; @Mock private ItemService mockedItemService = new ItemServiceImpl(); @BeforeEach public void beforeEach() { MockitoAnnotations.initMocks(this); } @Test public void calculatePrice_whenItemNotAvailable_throwsException() { // Arrange CustomerProfile customerProfile = new CustomerProfile(); customerProfile.setExtraLoyaltyDiscountPercentage(2.00); double expectedPrice = 176.00; // Setting up stubbed responses using mocks when(mockedUserService.getUser(anyInt())).thenReturn(customerProfile); when(mockedItemService.getItemDetails(anyInt())).thenThrow(new ItemServiceException(anyString())); // Act & Assert assertThrows(ItemServiceException.class, () -> priceCalculator.calculatePrice(123, 234)); }
Avec les exemples ci-dessus, j’ai essayé d’expliquer le concept de Mocks & Spies et comment ils peuvent être combinés pour créer des tests unitaires efficaces et utiles.
comment exécuter un fichier torrent
Il peut y avoir plusieurs combinaisons de ces techniques pour obtenir une suite de tests qui améliorent la couverture de la méthode testée, garantissant ainsi un haut niveau de confiance dans le code et le rendant plus résistant aux bogues de régression.
Code source
Interfaces
RemiseCalculateur
public interface DiscountCalculator { double calculateDiscount(ItemSku itemSku, double markedPrice); void calculateProfitability(ItemSku itemSku, CustomerProfile customerProfile); }
ItemService
public interface ItemService { ItemSku getItemDetails(int skuCode) throws ItemServiceException; }
UserService
public interface UserService { void addUser(CustomerProfile customerProfile); void deleteUser(CustomerProfile customerProfile); CustomerProfile getUser(int customerAccountId); }
Implémentations d'interface
DiscountCalculatorImpl
public class DiscountCalculatorImpl implements DiscountCalculator { @Override public double calculateDiscount(ItemSku itemSku, double markedPrice) { return 0; } @Override public void calculateProfitability(ItemSku itemSku, CustomerProfile customerProfile) { } }
ItemServiceImpl
public class DiscountCalculatorImpl implements DiscountCalculator { @Override public double calculateDiscount(ItemSku itemSku, double markedPrice) { return 0; } @Override public void calculateProfitability(ItemSku itemSku, CustomerProfile customerProfile) { } }
Des modèles
Profil client
public class CustomerProfile { private String customerName; private String loyaltyTier; private String customerAddress; private String accountId; private double extraLoyaltyDiscountPercentage; public double getExtraLoyaltyDiscountPercentage() { return extraLoyaltyDiscountPercentage; } public void setExtraLoyaltyDiscountPercentage(double extraLoyaltyDiscountPercentage) { this.extraLoyaltyDiscountPercentage = extraLoyaltyDiscountPercentage; } public String getAccountId() { return accountId; } public void setAccountId(String accountId) { this.accountId = accountId; } public String getCustomerName() { return customerName; } public void setCustomerName(String customerName) { this.customerName = customerName; } public String getLoyaltyTier() { return loyaltyTier; } public void setLoyaltyTier(String loyaltyTier) { this.loyaltyTier = loyaltyTier; } public String getCustomerAddress() { return customerAddress; } public void setCustomerAddress(String customerAddress) { this.customerAddress = customerAddress; } }
ObjetSku
public class ItemSku { private int skuCode; private double price; private double maxDiscount; private double margin; private int totalQuantity; private double applicableDiscount; public double getApplicableDiscount() { return applicableDiscount; } public void setApplicableDiscount(double applicableDiscount) { this.applicableDiscount = applicableDiscount; } public int getTotalQuantity() { return totalQuantity; } public void setTotalQuantity(int totalQuantity) { this.totalQuantity = totalQuantity; } public int getSkuCode() { return skuCode; } public void setSkuCode(int skuCode) { this.skuCode = skuCode; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } public double getMaxDiscount() { return maxDiscount; } public void setMaxDiscount(double maxDiscount) { this.maxDiscount = maxDiscount; } public double getMargin() { return margin; } public void setMargin(double margin) { this.margin = margin; } }
Classe en cours de test - PriceCalculator
public class PriceCalculator { public DiscountCalculator discountCalculator; public UserService userService; public ItemService itemService; public PriceCalculator(DiscountCalculator discountCalculator, UserService userService, ItemService itemService){ this.discountCalculator = discountCalculator; this.userService = userService; this.itemService = itemService; } public double calculatePrice(int itemSkuCode, int customerAccountId) { double price = 0; // get Item details ItemSku sku = itemService.getItemDetails(itemSkuCode); // get User and calculate price CustomerProfile customerProfile = userService.getUser(customerAccountId); double basePrice = sku.getPrice(); price = basePrice - (basePrice* (sku.getApplicableDiscount() + customerProfile.getExtraLoyaltyDiscountPercentage())/100); return price; } }
Tests unitaires - PriceCalculatorUnitTests
public class PriceCalculatorUnitTests { @InjectMocks private PriceCalculator priceCalculator; @Mock private DiscountCalculator mockedDiscountCalculator; @Mock private UserService mockedUserService; @Mock private ItemService mockedItemService; @BeforeEach public void beforeEach() { MockitoAnnotations.initMocks(this); } @Test public void calculatePrice_withCorrectInput_returnsExpectedPrice() { // Arrange ItemSku item1 = new ItemSku(); item1.setApplicableDiscount(5.00); item1.setPrice(100.00); CustomerProfile customerProfile = new CustomerProfile(); customerProfile.setExtraLoyaltyDiscountPercentage(2.00); double expectedPrice = 93.00; // Setting up stubbed responses using mocks when(mockedItemService.getItemDetails(anyInt())).thenReturn(item1); when(mockedUserService.getUser(anyInt())).thenReturn(customerProfile); // Act double actualPrice = priceCalculator.calculatePrice(123,5432); // Assert assertEquals(expectedPrice, actualPrice); } @Test @Disabled // to enable this change the ItemService MOCK to SPY public void calculatePrice_withCorrectInputRealMethodCall_returnsExpectedPrice() { // Arrange CustomerProfile customerProfile = new CustomerProfile(); customerProfile.setExtraLoyaltyDiscountPercentage(2.00); double expectedPrice = 176.00; // Setting up stubbed responses using mocks when(mockedUserService.getUser(anyInt())).thenReturn(customerProfile); // Act double actualPrice = priceCalculator.calculatePrice(2367,5432); // Assert assertEquals(expectedPrice, actualPrice); } @Test public void calculatePrice_whenItemNotAvailable_throwsException() { // Arrange CustomerProfile customerProfile = new CustomerProfile(); customerProfile.setExtraLoyaltyDiscountPercentage(2.00); double expectedPrice = 176.00; // Setting up stubbed responses using mocks when(mockedUserService.getUser(anyInt())).thenReturn(customerProfile); when(mockedItemService.getItemDetails(anyInt())).thenThrow(new ItemServiceException(anyString())); // Act & Assert assertThrows(ItemServiceException.class, () -> priceCalculator.calculatePrice(123, 234)); } }
Différents types de Matchers fournis par Mockito sont expliqués dans notre prochain tutoriel.
Tutoriel PREV | Tutoriel SUIVANT
lecture recommandée
- Différents types de matchers fournis par Mockito
- Tutoriel Mockito: Framework Mockito pour la simulation dans les tests unitaires
- Création de tests d'epochs à l'aide d'epochs Studio pour Eclipse
- Tutoriel Python DateTime avec des exemples
- Commande Cut sous Unix avec des exemples
- Syntaxe des commandes Unix Cat, options avec exemples
- Utilisation du curseur dans MongoDB avec des exemples
- Commande Ls sous Unix avec des exemples