creating mocks spies mockito with code examples
Tutorial Mockito Spy and Mocks:
In questo Serie di tutorial Mockito , il nostro tutorial precedente ci ha fornito un file Introduzione a Mockito Framework . In questo tutorial, impareremo il concetto di Mocks and Spies in Mockito.
Cosa sono i mock e le spie?
Sia Mock che Spies sono i tipi di doppioni di test, utili per la scrittura di unit test.
I mock sono un sostituto completo della dipendenza e possono essere programmati per restituire l'output specificato ogni volta che viene chiamato un metodo sul mock. Mockito fornisce un'implementazione predefinita per tutti i metodi di un mock.
Cosa imparerai:
- Cosa sono le spie?
- Creazione di mock
- Creazione di spie
- Come iniettare dipendenze fittizie per la classe / oggetto in prova?
- Suggerimenti e trucchi
- Esempi di codice - Spie e scherzi
- Codice sorgente
- Lettura consigliata
Cosa sono le spie?
Le spie sono essenzialmente un wrapper su un'istanza reale della dipendenza derisa. Ciò significa che richiede una nuova istanza dell'oggetto o della dipendenza e quindi aggiunge un wrapper dell'oggetto deriso su di esso. Per impostazione predefinita, le Spie chiamano metodi reali dell'Oggetto a meno che non vengano stub.
Le spie forniscono alcuni poteri aggiuntivi come quali argomenti sono stati forniti alla chiamata al metodo, se il metodo reale è stato chiamato, ecc.
In poche parole, per Spies:
- È richiesta l'istanza reale dell'oggetto.
- Spies offre flessibilità per bloccare alcuni (o tutti) i metodi dell'oggetto spiato. A quel tempo, la spia viene essenzialmente chiamata o riferita a un oggetto parzialmente deriso o mozzato.
- Le interazioni richiamate su un oggetto spiato possono essere monitorate per la verifica.
In generale, le spie non vengono utilizzate molto frequentemente ma possono essere utili per le applicazioni legacy di unit test in cui le dipendenze non possono essere completamente derise.
Per tutta la descrizione di Mock and Spy, ci riferiamo a una classe / oggetto fittizio chiamato 'DiscountCalculator' che vogliamo deridere / spiare.
Ha alcuni metodi come mostrato di seguito:
calcolaDiscount - Calcola il prezzo scontato di un determinato prodotto.
getDiscountLimit - Recupera il limite di sconto del limite superiore per il prodotto.
Creazione di mock
# 1) Creazione di mock con il codice
Mockito fornisce diverse versioni sovraccariche di Mockito. Mock metodo e consente di creare mock per le dipendenze.
Sintassi:
Mockito.mock(Class classToMock)
Esempio:
Supponiamo che il nome della classe sia DiscountCalculator, per creare un mock nel codice:
DiscountCalculator mockedDiscountCalculator = Mockito.mock(DiscountCalculator.class)
È importante notare che Mock può essere creato sia per l'interfaccia che per una classe concreta.
Quando un oggetto viene deriso, a meno che non vengano sottoposti a stub, tutti i metodi restituiscono null per impostazione predefinita .
DiscountCalculator mockDiscountCalculator = Mockito.mock(DiscountCalculator.class);
# 2) Creazione di mock con annotazioni
Invece di deridere utilizzando il metodo statico 'mock' della libreria Mockito, fornisce anche un modo abbreviato per creare mock utilizzando l'annotazione '@Mock'.
Il più grande vantaggio di questo approccio è che è semplice e consente di combinare dichiarazione ed essenzialmente inizializzazione. Rende inoltre i test più leggibili ed evita l'inizializzazione ripetuta di mock quando lo stesso mock viene utilizzato in più punti.
Per garantire l'inizializzazione Mock attraverso questo approccio, è necessario chiamare 'MockitoAnnotations.initMocks (this)' per la classe in prova. Questo è il candidato ideale per far parte del metodo 'beforeEach' di Junit che garantisce che i mock vengono inizializzati ogni volta che viene eseguito un test da quella classe.
Sintassi:
@Mock private transient DiscountCalculator mockedDiscountCalculator;
Creazione di spie
Simile a Mock, anche le spie possono essere create in 2 modi:
# 1) Creazione di spie con codice
Mockito.spy è il metodo statico utilizzato per creare un oggetto / wrapper 'spia' attorno all'istanza dell'oggetto reale.
Sintassi:
domande e risposte dell'intervista html per le matricole
private transient ItemService itemService = new ItemServiceImpl() private transient ItemService spiedItemService = Mockito.spy(itemService);
# 2) Creazione di spie con annotazioni
Simile a Mock, le spie possono essere create utilizzando l'annotazione @Spy.
Anche per l'inizializzazione della spia è necessario assicurarsi che MockitoAnnotations.initMocks (this) venga chiamato prima che la spia venga utilizzata nel test vero e proprio per ottenere l'inizializzazione della spia.
Sintassi:
@Spy private transient ItemService spiedItemService = new ItemServiceImpl();
Come iniettare dipendenze fittizie per la classe / oggetto in prova?
Quando vogliamo creare un oggetto fittizio della classe sotto test con le altre dipendenze fittizie, possiamo usare l'annotazione @InjectMocks.
Ciò che essenzialmente fa è che tutti gli oggetti contrassegnati con annotazioni @Mock (o @Spy) vengono iniettati come appaltatore o iniezione di proprietà nella classe Object e quindi le interazioni possono essere verificate sull'oggetto Mocked finale.
Ancora una volta, inutile dirlo, @InjectMocks è una scorciatoia contro la creazione di un nuovo Object della classe e fornisce oggetti derisi delle dipendenze.
Cerchiamo di capirlo con un esempio:
Supponiamo che esista una classe PriceCalculator, che ha DiscountCalculator e UserService come dipendenze che vengono iniettate tramite i campi Costruttore o Proprietà.
Quindi, per creare l'implementazione Mocked per la classe Price calculator, possiamo utilizzare 2 approcci:
# 1) Crea una nuova istanza di PriceCalculator e inietta dipendenze Mocked
@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 un'istanza derisa di PriceCalculator e inietta le dipendenze tramite l'annotazione @InjectMocks
apertura di file jar su Windows 10
@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'annotazione InjectMocks tenta effettivamente di iniettare dipendenze fittizie utilizzando uno degli approcci seguenti:
- Iniezione basata sul costruttore - Utilizza il Costruttore per la classe in prova.
- Metodi di settaggio basati - Quando un costruttore non è presente, Mockito cerca di iniettare utilizzando i setter di proprietà.
- Basato sul campo - Quando i 2 precedenti non sono disponibili, tenta direttamente di iniettare tramite i campi.
Suggerimenti e trucchi
# 1) Configurazione di stub diversi per chiamate diverse dello stesso metodo:
Quando un metodo stubbed viene chiamato più volte all'interno del metodo sottoposto a test (o il metodo stubbed è nel ciclo e si desidera restituire un output diverso ogni volta), è possibile impostare Mock in modo che restituisca una risposta stubbed diversa ogni volta.
Per esempio: Supponi di volerlo ItemService per restituire un elemento diverso per 3 chiamate consecutive e hai articoli dichiarati nel tuo metodo sotto test come Item1, Item2 e Item3, quindi puoi semplicemente restituirli per 3 chiamate consecutive utilizzando il codice seguente:
@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 }
#Due) Lanciare un'eccezione tramite Mock: Questo è uno scenario molto comune quando si desidera testare / verificare un downstream / dipendenza che genera un'eccezione e controllare il comportamento del sistema sottoposto a test. Tuttavia, per generare un'eccezione da parte di Mock, sarà necessario impostare lo stub utilizzando 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 corrispondenze come anyInt () e anyString (), non lasciarti intimidire perché verranno trattate nei prossimi articoli. Ma in sostanza, ti danno solo la flessibilità di fornire qualsiasi valore Integer e String rispettivamente senza argomenti di funzione specifici.
Esempi di codice - Spie e scherzi
Come discusso in precedenza, sia Spies che Mocks sono il tipo di doppiette di prova e hanno i propri usi.
Mentre le spie sono utili per testare applicazioni legacy (e dove i mock non sono possibili), per tutti gli altri metodi / classi testabili ben scritti, Mocks soddisfa la maggior parte delle esigenze di test unitario.
Per lo stesso esempio: Scriviamo un test utilizzando Mocks for PriceCalculator -> metodo preparePrice (il metodo calcola itemPrice meno degli sconti applicabili)
La classe PriceCalculator e il metodo in prova calcolaPrice hanno l'aspetto mostrato di seguito:
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; } }
Ora scriviamo un test positivo per questo metodo.
Stub userService e servizio articolo come indicato di seguito:
- UserService restituirà sempre CustomerProfile con loyaltyDiscountPercentage impostato su 2.
- ItemService restituirà sempre un articolo con il prezzo base di 100 e lo sconto applicabile di 5.
- Con i valori precedenti, il prezzo previsto restituito dal metodo in prova risulta essere 93 $.
Ecco il codice per il 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); }
Come puoi vedere, nel test precedente - Stiamo affermando che il prezzo effettivo restituito dal metodo è uguale al prezzo previsto, ovvero 93,00.
Ora, scriviamo un test utilizzando Spy.
Spiaremo ItemService e codificheremo l'implementazione ItemService in modo che restituisca sempre un articolo con il prezzo base 200 e lo sconto applicabile del 10.00% (il resto della configurazione fittizia rimane lo stesso) ogni volta che viene chiamato con skuCode di 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);
Ora, vediamo un file Esempio di un'eccezione lanciata da ItemService poiché la quantità di Item disponibile era 0. Configureremo mock per generare un'eccezione.
@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)); }
Con gli esempi precedenti, ho cercato di spiegare il concetto di Mock & Spies e come possono essere combinati per creare unit test efficaci e utili.
Possono esserci più combinazioni di queste tecniche per ottenere una suite di test che migliorano la copertura del metodo sottoposto a test, garantendo così un grande livello di fiducia nel codice e rendendolo più resistente ai bug di regressione.
Codice sorgente
Interfacce
DiscountCalculator
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); }
Implementazioni dell'interfaccia
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) { } }
Modelli
CustomerProfile
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 in 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; } }
Test unitari - 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)); } }
Diversi tipi di matcher forniti da Mockito sono spiegati nel nostro prossimo tutorial.
Tutorial PREV | PROSSIMO Tutorial
Lettura consigliata
- Diversi tipi di matcher forniti da Mockito
- Tutorial Mockito: Mockito Framework per Mocking in Unit Testing
- Creazione di test di epoche utilizzando epochs Studio for Eclipse
- Tutorial Python DateTime con esempi
- Comando Taglia in Unix con esempi
- Sintassi dei comandi Cat Unix, opzioni con esempi
- Utilizzo del cursore in MongoDB con esempi
- Comando Ls in Unix con esempi