creating mocks spies mockito with code examples
Vejledning til Mockito Spy og Mocks:
I denne Mockito Tutorial-serie , vores tidligere tutorial gav os en Introduktion til Mockito Framework . I denne vejledning lærer vi begrebet Mocks and Spies i Mockito.
Hvad er Mocks and Spies?
Både Mocks og Spies er de typer testdobler, som er nyttige til at skrive enhedstest.
Mocks er en fuld erstatning for afhængighed og kan programmeres til at returnere det specificerede output, når der kaldes en metode på mocken. Mockito giver en standardimplementering for alle metoder til en mock.
Hvad du lærer:
- Hvad er spioner?
- Oprettelse af Mocks
- Oprettelse af spioner
- Hvordan indsprøjtes spottede afhængigheder for den klasse / objekt, der testes?
- Tips & tricks
- Kodeeksempler - Spioner og mocks
- Kildekode
- Anbefalet læsning
Hvad er spioner?
Spioner er i det væsentlige en indpakning i en reel forekomst af den hånede afhængighed. Hvad dette betyder er, at det kræver en ny forekomst af objektet eller afhængighed og derefter tilføjer en indpakning af det spottede objekt over det. Som standard kalder Spioner rigtige metoder til objektet, medmindre de er stubbet.
Spioner leverer visse ekstra beføjelser som hvilke argumenter der blev leveret til metodekaldet, var den virkelige metode overhovedet kaldet osv.
I en nøddeskal for spioner:
- Den virkelige forekomst af objektet er påkrævet.
- Spioner giver fleksibilitet til at stoppe nogle (eller alle) metoder til det spionerede objekt. På det tidspunkt kaldes spionen i det væsentlige eller henvises til en delvist hånet eller stubbet genstand.
- Interaktionerne, der kaldes på et spioneret objekt, kan spores til verifikation.
Generelt bruges spioner ikke særlig ofte, men kan være nyttige til enhedstest af ældre applikationer, hvor afhængighederne ikke kan spottes fuldt ud.
For hele beskrivelsen af Mock and Spy henviser vi til en fiktiv klasse / objekt kaldet 'DiscountCalculator', som vi ønsker at spotte / spionere.
Det har nogle metoder som vist nedenfor:
calcDiscount - Beregner den nedsatte pris for et givet produkt.
getDiscountLimit - Henter den øvre grænse for rabat på produktet.
Oprettelse af Mocks
# 1) Mock-oprettelse med kode
Mockito giver flere overbelastede versioner af Mockito. Mocks-metode og tillader oprettelse af mocks til afhængigheder.
Syntaks:
Mockito.mock(Class classToMock)
Eksempel:
Antag, at klassenavn er DiscountCalculator for at oprette en mock i kode:
DiscountCalculator mockedDiscountCalculator = Mockito.mock(DiscountCalculator.class)
Det er vigtigt at bemærke, at Mock kan oprettes til både interface eller en konkret klasse.
Når et objekt spottes, medmindre stubbet returnerer alle metoder som standard null .
DiscountCalculator mockDiscountCalculator = Mockito.mock(DiscountCalculator.class);
# 2) Skab oprettelse med kommentarer
I stedet for at spotte ved hjælp af statisk 'mock' -metode i Mockito-biblioteket, giver det også en kortfattet måde at oprette mocks på ved hjælp af '@Mock' -notering.
Den største fordel ved denne tilgang er, at den er enkel og gør det muligt at kombinere erklæring og i det væsentlige initialisering. Det gør også testene mere læsbare og undgår gentagen initialisering af mocks, når den samme mock bruges flere steder.
For at sikre Mock-initialisering gennem denne tilgang krævede vi, at vi skulle kalde 'MockitoAnnotations.initMocks (this)' til den klasse, der testes. Dette er den ideelle kandidat til at være en del af 'beforeEach' -metoden fra Junit, som sikrer, at mocks initialiseres hver gang, når en test udføres fra den klasse.
Syntaks:
@Mock private transient DiscountCalculator mockedDiscountCalculator;
Oprettelse af spioner
I lighed med Mocks kan spioner også oprettes på to måder:
# 1) Oprettelse af spion med kode
Mockito.spy er den statiske metode, der bruges til at oprette et 'spion' -objekt / -indpakning omkring den virkelige objektforekomst.
Syntaks:
hvilket af følgende er stien til knappen 'tag et skærmbillede'?
private transient ItemService itemService = new ItemServiceImpl() private transient ItemService spiedItemService = Mockito.spy(itemService);
# 2) Oprettelse af spion med kommentarer
Svarende til Mock kan spioner oprettes ved hjælp af @Spy-kommentar.
Også til Spy-initialisering skal du sikre dig, at MockitoAnnotations.initMocks (dette) kaldes, før Spy bruges i selve testen for at få spionen initialiseret.
Syntaks:
@Spy private transient ItemService spiedItemService = new ItemServiceImpl();
Hvordan indsprøjtes spottede afhængigheder for den klasse / objekt, der testes?
Når vi ønsker at oprette et mock-objekt af den klasse, der testes med de andre hånede afhængigheder, kan vi bruge @InjectMocks-kommentaren.
Hvad dette i det væsentlige gør er, at alle objekter, der er markeret med @Mock (eller @Spy) -noteringer, indsprøjtes som entreprenør- eller ejendomsinjektion i klassen Objekt, og interaktioner kan derefter verificeres på det endelige Mocked-objekt.
Igen, overflødigt at nævne, er @InjectMocks en stenografi mod at oprette et nyt objekt i klassen og giver spottede objekter af afhængighederne.
Lad os forstå dette med et eksempel:
Antag, der er en klasse PriceCalculator, som har DiscountCalculator og UserService som afhængigheder, der injiceres via Constructor- eller Property-felter.
Så for at skabe Mocked implementering til klasse lommeregner kan vi bruge to tilgange:
# 1) Opret en ny forekomst af PriceCalculator og injicere Mocked afhængigheder
@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) Opret en hånet forekomst af PriceCalculator og injicerer afhængigheder gennem @InjectMocks-kommentar
@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-kommentar forsøger faktisk at injicere spottede afhængigheder ved hjælp af en af nedenstående fremgangsmåder:
- Konstruktørbaseret injektion - Udnytter konstruktør til den klasse, der testes.
- Setter Methods Based - Når en konstruktør ikke er der, forsøger Mockito at injicere ved hjælp af ejendomssættere.
- Feltbaseret - Når ovenstående 2 ikke er tilgængelige, forsøger den direkte at indsprøjte via felter.
Tips & tricks
# 1) Opsætning af forskellige stubber til forskellige opkald af samme metode:
Når en stubmet metode kaldes flere gange inde i metoden, der testes (eller den stubede metode er i sløjfen, og du vil returnere forskellige output hver gang), kan du indstille Mock til at returnere forskellige stubbed-respons hver gang.
For eksempel: Antag at du vil ItemService for at returnere en anden vare til 3 på hinanden følgende opkald, og du har deklareret varer i din metode under test som Item1, Item2 og Item3, så kan du simpelthen returnere dem til 3 på hinanden følgende påkaldelser ved hjælp af nedenstående kode:
@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 undtagelse gennem Mock: Dette er et meget almindeligt scenarie, når du vil teste / verificere en downstream / afhængighed, der kaster en undtagelse og kontrollere opførelsen af det system, der testes. For at kaste en undtagelse fra Mock skal du dog indstille stub ved hjælp af 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 kampe som anyInt () og anyString () må du ikke blive skræmt, da de vil blive dækket i de kommende artikler. Men i det væsentlige giver de dig bare fleksibilitet til at give henholdsvis en heltal og strengværdi uden nogen specifikke funktionsargumenter.
Kodeeksempler - Spioner og mocks
Som tidligere diskuteret er både spioner og mocks typen af testdobler og har deres egne anvendelser.
Mens spioner er nyttige til test af ældre applikationer (og hvor mocks ikke er mulige), er Mocks tilstrækkeligt for alle de andre pænt skrevne testbare metoder / klasser, de fleste af enhedens testbehov.
For det samme eksempel: Lad os skrive en test ved hjælp af Mocks for PriceCalculator -> beregnePrismetode (Metoden beregner varePris minus de gældende rabatter)
PriceCalculator-klassen og metoden under test beregne pris ser ud 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; } }
Lad os nu skrive en positiv test for denne metode.
Vi vil stoppe userService og varetjeneste som nævnt nedenfor:
- UserService returnerer altid CustomerProfile med loyaltyDiscountPercentage indstillet til 2.
- ItemService returnerer altid en artikel med basisprisen på 100 og gældende rabat på 5.
- Med ovenstående værdier udgør den forventede pris, der returneres ved metoden under test, at være 93 $.
Her er koden til 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 ovenstående test - Vi hævder, at den faktiske pris, der returneres ved metoden, er lig med den forventede pris, dvs. 93,00.
Lad os nu skrive en test ved hjælp af Spy.
Vi spionerer vareservicen og koder implementeringen af vareservicen på en måde, så den altid returnerer en vare med basisprisen 200 og anvendelig rabat på 10,00% (resten af mock-opsætningen forbliver den samme), når den kaldes med skuCode på 2367.
selen interview spørgsmål og svar i 4 års erfaring
@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);
Lad os nu se en Eksempel af en undtagelse, der blev kastet af ItemService, da den tilgængelige varemængde var 0. Vi opretter mock til at kaste en undtagelse.
@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 ovenstående eksempler har jeg forsøgt at forklare begrebet Mocks & Spies, og hvordan de kan kombineres for at skabe effektive og nyttige enhedstests.
Der kan være flere kombinationer af disse teknikker for at få en række tests, der forbedrer dækningen af den metode, der testes, og derved sikre et stort niveau af tillid til koden og gør koden mere modstandsdygtig over for regressionsfejl.
Kildekode
Grænseflader
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); }
Interfaceimplementeringer
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) { } }
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; } }
Klasse 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; } }
Enhedstest - 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)); } }
Forskellige typer matchere leveret af Mockito forklares i vores kommende vejledning.
PREV-vejledning | NÆSTE vejledning
Anbefalet læsning
- Forskellige typer af matchere leveret af Mockito
- Mockito Tutorial: Mockito Framework for Mocking in Unit Testing
- Oprettelse af epoketests ved hjælp af epoker Studio for Eclipse
- Python DateTime-tutorial med eksempler
- Klip kommando i Unix med eksempler
- Unix Cat Command Syntax, indstillinger med eksempler
- Brug af markør i MongoDB med eksempler
- Ls Command i Unix med eksempler