creating mocks spies mockito with code examples
Tutorial de Mockito Spy and Mocks:
En aquest Mockito Tutorial sèries , el nostre tutorial anterior ens va donar un Introducció a Mockito Framework . En aquest tutorial, aprendrem el concepte de Mocks and Spies in Mockito.
Què són els simulacres i els espies?
Tant els simulacres com els espies són els tipus de dobles de proves, que són útils per escriure proves unitàries.
Els simuladors són un substitut complet de la dependència i es poden programar per retornar la sortida especificada sempre que es crida a un mètode del simulador. Mockito proporciona una implementació predeterminada per a tots els mètodes d'una simulació.
Què aprendreu:
- Què són els espies?
- Creació de simulacres
- Creació d’espies
- Com injectar dependències burlades per a la classe / objecte sotmès a prova?
- Consells i trucs
- Exemples de codi: espies i burles
- Codi font
- Lectura recomanada
Què són els espies?
Els espies són essencialment un embolcall en una instància real de la dependència burlada. El que vol dir això és que requereix una nova instància de l’objecte o dependència i, a continuació, hi afegeix un embolcall de l’objecte burlat. Per defecte, els espies anomenen mètodes reals de l'objecte tret que siguin obstaculitzats.
Els espies proporcionen certs poders addicionals, com ara quins arguments es van proporcionar a la crida al mètode, era el mètode real anomenat, etc.
En poques paraules, per a Spies:
- Es requereix la instància real de l'objecte.
- Spies dóna flexibilitat per evitar alguns (o tots) mètodes de l'objecte espiat. En aquella època, l’espia es diu o es refereix essencialment a un objecte parcialment burlat o obstinat.
- Les interaccions cridades a un objecte espiat es poden fer un seguiment per verificar-les.
En general, els espies no s’utilitzen amb molta freqüència, però poden ser útils per a proves d’unitats d’aplicacions heretades en què no es poden burlar completament les dependències.
Per a tota la descripció de Mock and Spy, ens referim a una classe / objecte fictici anomenat 'DiscountCalculator' que volem burlar / espiar.
Té alguns mètodes com es mostra a continuació:
calculateDiscount - Calcula el preu amb descompte d’un producte determinat.
getDiscountLimit - Obté el límit màxim de descompte del producte.
Creació de simulacres
# 1) Creació simulada amb Code
Mockito proporciona diverses versions de Mockito sobrecarregades. Mètode de simulacions i permet crear simulacions per a dependències.
Sintaxi:
Mockito.mock(Class classToMock)
Exemple:
Suposem que el nom de la classe és DiscountCalculator, per crear un simulador de codi:
DiscountCalculator mockedDiscountCalculator = Mockito.mock(DiscountCalculator.class)
És important tenir en compte que Mock es pot crear tant per a una interfície com per a una classe concreta.
Quan es burla un objecte, tret que es retiri tots els mètodes tornen nuls per defecte .
DiscountCalculator mockDiscountCalculator = Mockito.mock(DiscountCalculator.class);
# 2) Creació simulada amb Anotacions
En lloc de burlar-se del mètode estàtic de 'mock' de la biblioteca Mockito, també proporciona una forma abreujada de crear simulacions mitjançant l'anotació '@Mock'.
El major avantatge d’aquest enfocament és que és senzill i permet combinar declaració i inicialització bàsicament. També fa que les proves siguin més llegibles i eviti la inicialització repetida de simulacres quan s’utilitza la mateixa simulació en diversos llocs.
Per tal de garantir la inicialització de Mock mitjançant aquest enfocament, cal que anomenem 'MockitoAnnotations.initMocks (this)' per a la classe que es prova. Aquest és el candidat ideal per formar part del mètode 'beforeEach' de Junit, que garanteix que les simulacions s'inicialitzen cada vegada que s'executa una prova des d'aquesta classe.
Sintaxi:
@Mock private transient DiscountCalculator mockedDiscountCalculator;
Creació d’espies
De manera similar a Mocks, els espies també es poden crear de dues maneres:
# 1) Creació d’espies amb Code
Mockito.spy és el mètode estàtic que s’utilitza per crear un objecte / embolcall ‘espia’ al voltant de la instància d’objecte real.
Sintaxi:
preguntes i respostes d’entrevistes de disseny de bases de dades
private transient ItemService itemService = new ItemServiceImpl() private transient ItemService spiedItemService = Mockito.spy(itemService);
# 2) Creació d’espies amb anotacions
De manera similar a Mock, es poden crear espies mitjançant l’anotació @Spy.
Per a la inicialització de l'espia, també heu d'assegurar-vos que MockitoAnnotations.initMocks (això) es cridi abans que s'utilitzi l'espia a la prova real per tal d'inicialitzar l'espia.
Sintaxi:
@Spy private transient ItemService spiedItemService = new ItemServiceImpl();
Com injectar dependències burlades per a la classe / objecte sotmès a prova?
Quan volem crear un objecte simulat de la classe sotmesa a prova amb les altres dependències simulades, podem utilitzar l’anotació @InjectMocks.
El que fa essencialment això és que tots els objectes marcats amb les anotacions @Mock (o @Spy) s'injecten com a contractista o injecció de propietats a la classe Object i que es puguin verificar les interaccions a l'objecte Mocked final.
Una vegada més, no cal esmentar-ho, @InjectMocks és una abreviatura contra la creació d'un nou objecte de la classe i proporciona objectes burlats de les dependències.
Entenguem això amb un exemple:
Suposem que hi ha una classe PriceCalculator, que té DiscountCalculator i UserService com a dependències que s’injecten mitjançant els camps Constructor o Property.
Per tant, per tal de crear la implementació burlada de la classe de calculadores de preus, podem utilitzar dos enfocaments:
# 1) Crea una nova instància de PriceCalculator i injectar dependències burlades
@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) Crea una instància burlada de PriceCalculator i injectar dependències mitjançant l’anotació @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'anotació InjectMocks intenta injectar dependències burlades mitjançant un dels enfocaments següents:
- Injecció basada en constructors - Utilitza Constructor per a la classe que es prova.
- Basat en mètodes de setter - Quan un constructor no hi és, Mockito intenta injectar-se mitjançant assentadors de propietats.
- Basat en el camp - Quan els 2 anteriors no estan disponibles, s'intenta directament injectar mitjançant camps.
Consells i trucs
# 1) Configuració de talons diferents per a diferents trucades del mateix mètode:
Quan es crida un mètode tallat diverses vegades dins del mètode que es prova (o el mètode tallat es troba al bucle i voleu retornar una sortida diferent cada vegada), podeu configurar Mock per retornar cada cop una resposta diferent.
Per exemple: Suposem que voleu ItemService per retornar un ítem diferent durant 3 trucades consecutives i que tingueu ítems declarats al vostre mètode en proves com a ítem 1, ítem 2 i ítem 3, simplement podeu retornar-los per 3 invocacions consecutives mitjançant el codi següent:
@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 }
# 2) Llançar una excepció a través de Mock: Aquest és un escenari molt comú quan es vol provar / verificar una dependència descendent / una excepció i comprovar el comportament del sistema que es prova. Tanmateix, per tal de llançar una excepció per Mock, haureu de configurar stub mitjançant 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 }
Per a partits com anyInt () i anyString (), no us espanteu, ja que es tractaran en els propers articles. Però, en essència, només us proporcionen la flexibilitat per proporcionar qualsevol valor enter i de cadena, respectivament, sense cap argument de funció específic.
Exemples de codi: espies i burles
Com s’ha comentat anteriorment, tant els espies com els simulacres són el tipus de prova de dobles i tenen els seus propis usos.
Tot i que els espies són útils per provar aplicacions heretades (i on no són possibles simulacions), per a la resta de mètodes / classes que es poden provar molt bé escrits, els simulacres són suficients per a la majoria de les necessitats de proves de la Unitat.
Per al mateix exemple: Escrivim una prova amb Mocks for PriceCalculator -> mètode calculatePrice (El mètode calcula el preu de l’article menys dels descomptes aplicables)
La classe PriceCalculator i el mètode a prova calculatePrice es veuen com es mostra a continuació:
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; } }
Ara anem a escriure una prova positiva d’aquest mètode.
Eliminarem el servei d’usuaris i el servei d’elements tal com s’esmenta a continuació:
- UserService sempre retornarà CustomerProfile amb el loyaltyDiscountPercentage definit a 2.
- ItemService sempre retornarà un article amb el preu base de 100 i el descompte aplicable de 5.
- Amb els valors anteriors, el preu esperat retornat pel mètode sotmès a prova és de 93 $.
Aquí teniu el codi per fer la prova:
@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); }
Com podeu veure, a la prova anterior: afirmem que el preu real retornat pel mètode és igual al preu esperat, és a dir, 93,00.
Ara, escrivim una prova amb Spy.
Espiarem ItemService i codificarem la implementació ItemService de manera que sempre retorni un article amb el basePrice 200 i el descompte aplicable del 10,00% (la resta de la configuració simulada continua sent la mateixa) sempre que es cridi amb 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);
Ara, anem a veure un Exemple d'una excepció que va llançar ItemService, ja que la quantitat d'article disponible era 0. Configurarem una simulació per llançar una excepció.
implementació d’arbre binari c ++
@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)); }
Amb els exemples anteriors, he intentat explicar el concepte de Mocks & Spies i com es poden combinar per crear proves d’unitat efectives i útils.
Hi pot haver múltiples combinacions d’aquestes tècniques per obtenir un conjunt de proves que millorin la cobertura del mètode que es prova, garantint així un gran nivell de confiança en el codi i el fa més resistent als errors de regressió.
Codi font
Interfícies
Calculador de descomptes
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); }
Implementacions d'interfície
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) { } }
Models
Perfil del 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; } }
ItemSku
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 sota prova: 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; } }
Proves d’unitat: 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)); } }
Al nostre proper tutorial s’expliquen els diferents tipus de coincidències que proporciona Mockito.
Lectura recomanada
- Diferents tipus de coincidències proporcionats per Mockito
- Tutorial de Mockito: marc Mockito per burlar-se de les proves unitàries
- Creació de proves d’època mitjançant epochs Studio for Eclipse
- Tutorial de Python DateTime amb exemples
- Talla l'ordre a Unix amb exemples
- Sintaxi d'ordres Unix Cat, opcions amb exemples
- Ús del cursor a MongoDB amb exemples
- Ordre Ls a Unix amb exemples