creating mocks spies mockito with code examples
Samouczek Mockito Spy and Mocks:
W tym Seria samouczków Mockito , nasz poprzedni samouczek dał nam plik Wprowadzenie do Mockito Framework . W tym samouczku poznamy koncepcję Mocks and Spies w Mockito.
Co to są Mocks and Spies?
Zarówno Mocks, jak i Spies to typy podwójnych testów, które są pomocne w pisaniu testów jednostkowych.
Makiety zastępują zależność i można je zaprogramować tak, aby zwracały określone dane wyjściowe za każdym razem, gdy wywoływana jest metoda na makiecie. Mockito zapewnia domyślną implementację dla wszystkich metod makiety.
Czego się nauczysz:
- Kim są szpiedzy?
- Tworzenie mocków
- Tworzenie szpiegów
- Jak wstrzyknąć mockowane zależności dla testowanej klasy / obiektu?
- Porady & Triki
- Przykłady kodu - szpiedzy i drwiny
- Kod źródłowy
- rekomendowane lektury
Kim są szpiedzy?
Szpiedzy są w istocie opakowaniem rzeczywistej instancji wyśmiewanej zależności. Oznacza to, że wymaga nowej instancji obiektu lub zależności, a następnie dodaje otokę obiektu mockowanego. Domyślnie, Szpiegowie wywołują prawdziwe metody Obiektu, chyba że są zablokowane.
Szpiedzy zapewniają pewne dodatkowe uprawnienia, takie jak to, jakie argumenty zostały dostarczone do wywołania metody, czy w ogóle wywołano prawdziwą metodę itp.
Krótko mówiąc, dla szpiegów:
- Wymagana jest rzeczywista instancja obiektu.
- Szpiedzy dają elastyczność w usuwaniu niektórych (lub wszystkich) metod szpiegowanego obiektu. W tym czasie szpieg jest zasadniczo nazywany lub określany jako częściowo wyszydzony lub zablokowany obiekt.
- Interakcje wywołane na szpiegowanym obiekcie można śledzić w celu weryfikacji.
Ogólnie rzecz biorąc, programy szpiegowskie nie są często używane, ale mogą być pomocne przy testach jednostkowych starszych aplikacji, w których zależności nie można w pełni wyszydzać.
W całym opisie Mock and Spy mamy na myśli fikcyjną klasę / obiekt o nazwie „DiscountCalculator”, który chcemy podszywać / szpiegować.
Ma kilka metod, jak pokazano poniżej:
oblicz zniżkę - Oblicza zdyskontowaną cenę danego produktu.
getDiscountLimit - Pobiera górny limit rabatu na produkt.
Tworzenie mocków
# 1) Tworzenie makiety za pomocą kodu
Mockito udostępnia kilka przeciążonych wersji Mockito. Mocks i pozwala na tworzenie mocków dla zależności.
Składnia:
Mockito.mock(Class classToMock)
Przykład:
Załóżmy, że nazwa klasy to DiscountCalculator, aby utworzyć makietę w kodzie:
DiscountCalculator mockedDiscountCalculator = Mockito.mock(DiscountCalculator.class)
Należy zauważyć, że Mock można utworzyć zarówno dla interfejsu, jak i dla konkretnej klasy.
Gdy obiekt jest wyszydzany, chyba że skrótowe wszystkie metody domyślnie zwracają wartość null .
DiscountCalculator mockDiscountCalculator = Mockito.mock(DiscountCalculator.class);
# 2) Tworzenie makiety z adnotacjami
Zamiast mockowania przy użyciu statycznej metody „mock” biblioteki Mockito, zapewnia również skróconą metodę tworzenia mocków za pomocą adnotacji „@Mock”.
Największą zaletą tego podejścia jest to, że jest proste i pozwala na połączenie deklaracji i zasadniczo inicjalizacji. Sprawia również, że testy są bardziej czytelne i pozwala uniknąć wielokrotnej inicjalizacji makiet, gdy ten sam makiet jest używany w kilku miejscach.
Aby zapewnić inicjalizację Mocka za pomocą tego podejścia, wymagało to wywołania „MockitoAnnotations.initMocks (this)” dla testowanej klasy. Jest to idealny kandydat do bycia częścią metody „beforeEach” programu Junit, która zapewnia, że makiety są inicjowane za każdym razem, gdy wykonywany jest test z tej klasy.
Składnia:
@Mock private transient DiscountCalculator mockedDiscountCalculator;
Tworzenie szpiegów
Podobnie jak Mocks, Szpiegów można również tworzyć na dwa sposoby:
# 1) Tworzenie szpiegów za pomocą kodu
Mockito.spy to statyczna metoda używana do tworzenia „szpiegowskiego” obiektu / opakowania wokół rzeczywistej instancji obiektu.
Składnia:
oprogramowanie napisane w języku c ++
private transient ItemService itemService = new ItemServiceImpl() private transient ItemService spiedItemService = Mockito.spy(itemService);
# 2) Tworzenie szpiegów z adnotacjami
Podobnie jak Mock, Szpiegów można tworzyć za pomocą adnotacji @Spy.
Również w przypadku inicjalizacji Szpiega musisz upewnić się, że MockitoAnnotations.initMocks (this) są wywoływane przed użyciem Szpiega w rzeczywistym teście, aby zainicjować szpiega.
Składnia:
@Spy private transient ItemService spiedItemService = new ItemServiceImpl();
Jak wstrzyknąć mockowane zależności dla testowanej klasy / obiektu?
Gdy chcemy stworzyć obiekt pozorowany testowanej klasy z innymi mockowanymi zależnościami, możemy użyć adnotacji @InjectMocks.
Zasadniczo polega to na tym, że wszystkie obiekty oznaczone adnotacjami @Mock (lub @Spy) są wstrzykiwane jako wykonawca lub wstrzyknięcie właściwości do klasy Object, a następnie można zweryfikować interakcje na końcowym obiekcie Mocked.
Ponownie, nie trzeba wspominać, @InjectMocks jest skrótem przeciwko tworzeniu nowego obiektu klasy i zapewnia mockowane obiekty zależności.
Zrozummy to na przykładzie:
Załóżmy, że istnieje klasa PriceCalculator, która ma DiscountCalculator i UserService jako zależności, które są wstrzykiwane za pośrednictwem pól Konstruktor lub Właściwość.
Tak więc, aby stworzyć implementację Mocked dla klasy Price calculator, możemy skorzystać z 2 podejść:
# 1) Utwórz nowe wystąpienie PriceCalculator i wstrzyknij Mocked zależności
@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) Utwórz symulowane wystąpienie PriceCalculator i wstrzykuj zależności za pomocą adnotacji @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);
Adnotacja InjectMocks faktycznie próbuje wstrzyknąć mockowane zależności przy użyciu jednego z poniższych podejść:
- Iniekcja oparta na konstruktorze - Wykorzystuje Konstruktora dla testowanej klasy.
- Oparte na metodach ustawiających - Gdy nie ma konstruktora, Mockito próbuje wykonać wstrzyknięcie za pomocą metod ustawiających właściwości.
- Oparte na terenie - Gdy powyższe 2 nie są dostępne, wówczas bezpośrednio próbuje wstrzyknąć przez pola.
Porady & Triki
# 1) Konfigurowanie różnych kodów pośredniczących dla różnych wywołań tej samej metody:
Gdy metoda stubbed jest wywoływana wiele razy wewnątrz testowanej metody (lub metoda stubbed jest w pętli i za każdym razem chcesz zwracać inne dane wyjściowe), możesz skonfigurować Mock, aby za każdym razem zwracał inną odpowiedź stubbed.
Na przykład: Przypuśćmy, że chcesz ItemService aby zwrócić inną pozycję dla 3 kolejnych wywołań, a masz pozycje zadeklarowane w metodzie w ramach testów jako Pozycja1, Pozycja2 i Pozycja3, możesz po prostu zwrócić je przez 3 kolejne wywołania, używając poniższego kodu:
@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 }
#dwa) Rzucanie wyjątku przez Mock: Jest to bardzo częsty scenariusz, gdy chcesz przetestować / zweryfikować podrzędny / zależność, wyrzucając wyjątek i sprawdzić zachowanie testowanego systemu. Jednak, aby zgłosić wyjątek przez Mock, będziesz musiał skonfigurować odgałęzienie za pomocą 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 }
W przypadku dopasowań takich jak anyInt () i anyString () nie daj się zastraszyć, ponieważ zostaną omówione w nadchodzących artykułach. Zasadniczo zapewniają one po prostu elastyczność w podawaniu odpowiednio dowolnej wartości całkowitej i ciągu bez żadnych konkretnych argumentów funkcji.
Przykłady kodu - szpiedzy i drwiny
Jak wspomniano wcześniej, zarówno Szpiegi, jak i Mocks są rodzajem testów podwójnych i mają swoje własne zastosowania.
Podczas gdy szpiedzy są przydatni do testowania starszych aplikacji (i gdy makiety nie są możliwe), dla wszystkich innych ładnie napisanych testowalnych metod / klas, Mocks wystarcza na większość potrzeb testowania jednostkowego.
W tym samym przykładzie: Napiszmy test używając Mocks for PriceCalculator -> metoda obliczPrice (metoda wylicza itemPrice pomniejszony o obowiązujące rabaty)
Klasa PriceCalculator i testowana metoda calculatorPrice wyglądają jak poniżej:
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; } }
Napiszmy teraz pozytywny test dla tej metody.
Zamierzamy usunąć usługę userService i item service, jak wspomniano poniżej:
- UserService zawsze zwraca CustomerProfile z loyaltyDiscountPercentage ustawionym na 2.
- ItemService zawsze zwróci Przedmiot z ceną podstawową równą 100 i odpowiednią zniżką w wysokości 5.
- Przy powyższych wartościach oczekiwana cena zwracana przez testowaną metodę wynosi 93 $.
Oto kod do testu:
@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); }
Jak widać w powyższym teście - Stwierdzamy, że faktyczna cena zwracana przez metodę jest równa oczekiwanej cenie, czyli 93,00.
Teraz napiszemy test przy użyciu Szpiega.
Będziemy szpiegować ItemService i zakodujemy implementację ItemService w taki sposób, aby zawsze zwracała element z ceną podstawową 200 i odpowiednią zniżką w wysokości 10,00% (reszta konfiguracji próbnej pozostaje taka sama) za każdym razem, gdy jest wywoływana z kodem skuCode 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);
Teraz zobaczmy Przykład wyjątku wyrzuconego przez ItemService, ponieważ dostępna ilość pozycji wynosiła 0. Skonfigurujemy makietę, aby zgłosić wyjątek.
@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)); }
Za pomocą powyższych przykładów próbowałem wyjaśnić koncepcję Mocks & Spies i jak można je łączyć, aby tworzyć skuteczne i przydatne testy jednostkowe.
Może istnieć wiele kombinacji tych technik, aby uzyskać zestaw testów, które zwiększają pokrycie testowanej metody, zapewniając w ten sposób wysoki poziom zaufania do kodu i zwiększając odporność kodu na błędy regresji.
Kod źródłowy
Interfejsy
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); }
Implementacje interfejsu
DiscountCalculatorImpl
programy, które ukrywają Twój adres IP
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) { } }
Modele
Profil klienta
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; } }
Klasa testowana - 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; } }
Testy jednostkowe - 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)); } }
Różne typy dopasowań zapewniane przez Mockito są wyjaśnione w naszym nadchodzącym samouczku.
POPRZEDNIA samouczek | NEXT Tutorial
rekomendowane lektury
- Różne typy dopasowań zapewniane przez Mockito
- Samouczek Mockito: Mockito Framework do mockowania w testach jednostkowych
- Tworzenie testów epok za pomocą epochs Studio for Eclipse
- Python DateTime Tutorial z przykładami
- Polecenie Cut w systemie Unix z przykładami
- Składnia poleceń Unix Cat, opcje z przykładami
- Wykorzystanie kursora w MongoDB z przykładami
- Polecenie Ls w systemie Unix z przykładami