creating mocks spies mockito with code examples
Mockito Spy and Mocks Tutorial:
I dette Mockito opplæringsserie , vår forrige opplæring ga oss en Introduksjon til Mockito Framework . I denne veiledningen vil vi lære konseptet Mocks and Spies i Mockito.
Hva er Mocks and Spies?
Både Mocks og Spies er typene testdobler, som er nyttige for å skrive enhetstester.
Mocks er en full erstatning for avhengighet og kan programmeres til å returnere den spesifiserte utgangen når en metode på mock kalles. Mockito gir en standardimplementering for alle metodene til en mock.
Hva du vil lære:
- Hva er Spioner?
- Opprette Mocks
- Å skape spioner
- Hvordan injisere spottede avhengigheter for klassen / objektet som testes?
- Tips og triks
- Kodeeksempler - Spioner og hån
- Kildekode
- Anbefalt lesing
Hva er Spioner?
Spioner er egentlig en innpakning på en reell forekomst av hånet avhengighet. Hva dette betyr er at det krever en ny forekomst av objektet eller avhengighet og deretter legger til en omslag av det spottede objektet over det. Som standard kaller spioner virkelige metoder for objektet, med mindre de er stubbet.
Spioner gir visse tilleggskrefter som hvilke argumenter som ble levert til metoden samtalen, var den virkelige metoden i det hele tatt kalt etc.
I et nøtteskall, for spioner:
- Den virkelige forekomsten av objektet er nødvendig.
- Spioner gir fleksibilitet til å stubbe noen (eller alle) metoder for det spionerte objektet. På den tiden kalles eller henvises spionen til et delvis hånet eller stukket objekt.
- Interaksjonene som kalles et spionert objekt kan spores for verifisering.
Generelt sett brukes ikke spioner veldig ofte, men kan være nyttige for enhetstesting av eldre applikasjoner der avhengighetene ikke kan spottes fullt ut.
For hele beskrivelsen av Mock and Spy, refererer vi til en fiktiv klasse / gjenstand kalt ‘DiscountCalculator’ som vi ønsker å spotte / spionere.
Den har noen metoder som vist nedenfor:
beregneDiscount - Beregner den nedsatte prisen for et gitt produkt.
getDiscountLimit - Henter den øvre grensen for rabattgrense for produktet.
Opprette Mocks
# 1) Spottopprettelse med kode
Mockito gir flere overbelastede versjoner av Mockito. Spotter metoden og tillater å lage spotter for avhengigheter.
Syntaks:
Mockito.mock(Class classToMock)
Eksempel:
Anta at klassenavn er DiscountCalculator, for å lage en mock i kode:
DiscountCalculator mockedDiscountCalculator = Mockito.mock(DiscountCalculator.class)
Det er viktig å merke seg at Mock kan opprettes for både grensesnitt eller en konkret klasse.
Når et objekt blir spottet, med mindre stubbe returnerer alle metodene null som standard .
DiscountCalculator mockDiscountCalculator = Mockito.mock(DiscountCalculator.class);
# 2) Spottopprettelse med kommentarer
I stedet for å spotte med statisk 'mock' -metoden i Mockito-biblioteket, gir den også en kort måte å lage mocks ved å bruke '@Mock' -kommentarer.
Den største fordelen med denne tilnærmingen er at den er enkel og gjør det mulig å kombinere erklæring og egentlig initialisering. Det gjør også testene mer lesbare og unngår gjentatt initialisering av mocks når samme mock brukes flere steder.
For å sikre Mock-initialisering gjennom denne tilnærmingen, krevdes det at vi skulle ringe 'MockitoAnnotations.initMocks (this)' for klassen som testes. Dette er den ideelle kandidaten til å være en del av 'beforeEach' -metoden til Junit, som sikrer at mocks blir initialisert hver gang når en test utføres fra den klassen.
Syntaks:
@Mock private transient DiscountCalculator mockedDiscountCalculator;
Å skape spioner
I likhet med Mocks kan Spies også opprettes på to måter:
# 1) Spionoppretting med kode
Mockito.spy er den statiske metoden som brukes til å lage et 'spion' -objekt / wrapper rundt den virkelige objektforekomsten.
Syntaks:
intervjuspørsmål om maven og jenkins
private transient ItemService itemService = new ItemServiceImpl() private transient ItemService spiedItemService = Mockito.spy(itemService);
# 2) Spionoppretting med merknader
I likhet med Mock kan Spies opprettes ved hjelp av @Spy-kommentar.
For spioninitialisering også må du sørge for at MockitoAnnotations.initMocks (dette) blir kalt før spionen brukes i selve testen for å få spionen initialisert.
Syntaks:
@Spy private transient ItemService spiedItemService = new ItemServiceImpl();
Hvordan injisere spottede avhengigheter for klassen / objektet som testes?
Når vi ønsker å lage et mock-objekt av klassen som testes med de andre hånede avhengighetene, kan vi bruke @InjectMocks-kommentaren.
Hva dette egentlig gjør er at alle objektene som er merket med @Mock (eller @Spy) -kommentarer, injiseres som entreprenør- eller eiendomsinjeksjon i klassen Objekt, og deretter kan interaksjoner verifiseres på det endelige Mocked-objektet.
Igjen, unødvendig å nevne, er @InjectMocks en stenografi mot å skape et nytt objekt i klassen og gir spottede objekter av avhengighetene.
La oss forstå dette med et eksempel:
Anta at det er en klasse PriceCalculator, som har DiscountCalculator og UserService som avhengigheter som injiseres via Constructor- eller Property-felt.
Så, for å lage Mocked implementering for Price calculator class, kan vi bruke to tilnærminger:
# 1) Opprett en ny forekomst av PriceCalculator og injisere Mocked avhengigheter
@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) Opprett en hånet forekomst av PriceCalculator og injiser avhengigheter gjennom @InjectMocks-kommentaren
@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);
InjectMocks-merknaden prøver faktisk å injisere spottede avhengigheter ved å bruke en av nedenstående tilnærminger:
- Konstruktørbasert injeksjon - Benytter Constructor for klassen som testes.
- Setter Methods Based - Når en konstruktør ikke er der, prøver Mockito å injisere ved hjelp av eiendomssettere.
- Feltbasert - Når de ovennevnte 2 ikke er tilgjengelige, prøver den direkte å injisere via felt.
Tips og triks
# 1) Sette opp forskjellige stubber for forskjellige samtaler av samme metode:
Når en stubmetode kalles flere ganger i metoden som testes (eller stubbed-metoden er i løkken, og du vil returnere forskjellig utgang hver gang), kan du konfigurere Mock til å returnere forskjellig stubbedrespons hver gang.
For eksempel: Anta at du vil ItemService for å returnere et annet element for 3 påfølgende samtaler, og du har gjenstander deklarert i metoden din under tester som Item1, Item2 og Item3, så kan du ganske enkelt returnere disse for 3 påfølgende påkallelser ved hjelp av koden nedenfor:
@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 }
#to) Kaster unntak gjennom Mock: Dette er et veldig vanlig scenario når du vil teste / verifisere en nedstrøms / avhengighet som kaster et unntak og sjekke oppførselen til systemet som testes. For å kaste et unntak fra Mock, må du imidlertid sette opp stubben ved hjelp av 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 }
For kamper som anyInt () og anyString (), ikke la deg skremme, da de blir omtalt i de kommende artiklene. Men i hovedsak gir de deg bare fleksibilitet til å gi henholdsvis en heltall og strengverdi uten spesifikke funksjonsargumenter.
Kodeeksempler - Spioner og hån
Som diskutert tidligere, både Spies og Mocks er typen testdobler og har sine egne bruksområder.
Mens spioner er nyttige for å teste eldre applikasjoner (og der mocks ikke er mulig), tilstrekkelig Mocks for alle de andre pent skrevne testbare metodene / klassene, de fleste av enhetens testbehov.
For samme eksempel: La oss skrive en test ved hjelp av Mocks for PriceCalculator -> calcifyPrice-metoden (Metoden beregner varenPris mindre av gjeldende rabatter)
PriceCalculator-klassen og metoden som testes, beregner prisen ser ut som vist nedenfor:
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; } }
La oss nå skrive en positiv test for denne metoden.
Vi kommer til å stoppe userService og varetjeneste som nevnt nedenfor:
- UserService returnerer alltid CustomerProfile med loyaltyDiscountPercentage satt til 2.
- ItemService returnerer alltid en vare med basePris på 100 og gjeldende rabatt på 5.
- Med de ovennevnte verdiene, blir forventet pris som returneres av metoden som testes til å være 93 $.
Her er koden for 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); }
Som du kan se, i testen ovenfor - Vi hevder at den faktiske prisen som returneres ved metoden er lik forventet pris, dvs. 93,00.
La oss nå skrive en test ved hjelp av Spy.
Vi vil spionere ItemService og vil kode ItemService-implementeringen på en slik måte at den alltid returnerer en vare med basePrice 200 og gjeldende rabatt på 10,00% (resten av mock-oppsettet forblir den samme) når det kalles med skuCode på 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);
La oss nå se en Eksempel av et unntak som ble kastet av ItemService ettersom det tilgjengelige varemengden var 0. Vi vil sette opp mock for å kaste et unntak.
@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)); }
Med eksemplene ovenfor har jeg prøvd å forklare begrepet Mocks & Spies og hvordan de kan kombineres for å skape effektive og nyttige enhetstester.
Det kan være flere kombinasjoner av disse teknikkene for å få en rekke tester som forbedrer dekningen av metoden som testes, og derved sikre stor grad av tillit til koden og gjør koden mer motstandsdyktig mot regresjonsfeil.
Kildekode
Grensesnitt
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); }
Grensesnittimplementeringer
RabattKalkulatorImpl
public class DiscountCalculatorImpl implements DiscountCalculator { @Override public double calculateDiscount(ItemSku itemSku, double markedPrice) { return 0; } @Override public void calculateProfitability(ItemSku itemSku, CustomerProfile customerProfile) { } }
ItemServiceImpl
hvor mye kan du tjene på brukertesting
public class DiscountCalculatorImpl implements DiscountCalculator { @Override public double calculateDiscount(ItemSku itemSku, double markedPrice) { return 0; } @Override public void calculateProfitability(ItemSku itemSku, CustomerProfile customerProfile) { } }
Modeller
Kundeprofil
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; } }
Class Under 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; } }
Enhetstester - 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)); } }
Ulike typer matchere levert av Mockito er forklart i vår kommende opplæring.
PREV Opplæring | NESTE veiledning
Anbefalt lesing
- Ulike typer matchere levert av Mockito
- Mockito Tutorial: Mockito Framework for Mocking in Unit Testing
- Opprette epoketester ved hjelp av epoker Studio for Eclipse
- Python DateTime Tutorial med eksempler
- Klipp kommandoen i Unix med eksempler
- Unix Cat Command Syntax, Alternativer med eksempler
- Bruk av markør i MongoDB med eksempler
- Ls Command i Unix med eksempler