creating mocks spies mockito with code examples
Mockito Spy und Mocks Tutorial:
In diesem Mockito Tutorial-Serie , unser vorheriges Tutorial gab uns eine Einführung in Mockito Framework . In diesem Tutorial lernen wir das Konzept von Mocks and Spies in Mockito kennen.
Was sind Mocks und Spione?
Sowohl Mocks als auch Spies sind die Arten von Test-Doubles, die beim Schreiben von Unit-Tests hilfreich sind.
Mocks sind ein vollständiger Ersatz für Abhängigkeiten und können so programmiert werden, dass sie die angegebene Ausgabe zurückgeben, wenn eine Methode für das Mock aufgerufen wird. Mockito bietet eine Standardimplementierung für alle Methoden eines Mocks.
Was du lernen wirst:
- Was sind Spione?
- Mocks erstellen
- Spione erstellen
- Wie injiziere ich verspottete Abhängigkeiten für die zu testende Klasse / das zu testende Objekt?
- Tipps
- Codebeispiele - Spies & Mocks
- Quellcode
- Literatur-Empfehlungen
Was sind Spione?
Spione sind im Wesentlichen ein Wrapper für eine reale Instanz der verspotteten Abhängigkeit. Dies bedeutet, dass eine neue Instanz des Objekts oder der Abhängigkeit erforderlich ist und dann ein Wrapper des verspotteten Objekts darüber hinzugefügt wird. Standardmäßig rufen Spies echte Methoden des Objekts auf, sofern sie nicht gestoppt werden.
Spione bieten bestimmte zusätzliche Befugnisse, z. B. welche Argumente für den Methodenaufruf angegeben wurden, ob die eigentliche Methode überhaupt aufgerufen wurde usw.
Kurz gesagt, für Spies:
- Die reale Instanz des Objekts ist erforderlich.
- Spies bietet Flexibilität, um einige (oder alle) Methoden des ausspionierten Objekts zu stoppen. Zu diesem Zeitpunkt wird der Spion im Wesentlichen als teilweise verspottetes oder stoppeliges Objekt bezeichnet oder bezeichnet.
- Die Interaktionen, die für ein ausspioniertes Objekt aufgerufen werden, können zur Überprüfung verfolgt werden.
Im Allgemeinen werden Spione nicht sehr häufig verwendet, können jedoch beim Testen von Legacy-Anwendungen hilfreich sein, bei denen die Abhängigkeiten nicht vollständig verspottet werden können.
Bei der gesamten Beschreibung von Mock and Spy beziehen wir uns auf eine fiktive Klasse / ein fiktives Objekt namens 'DiscountCalculator', die wir verspotten / ausspionieren möchten.
Es gibt einige Methoden wie unten gezeigt:
calculatorDiscount - Berechnet den reduzierten Preis eines bestimmten Produkts.
getDiscountLimit - Ruft die Obergrenze des Rabattlimits für das Produkt ab.
Mocks erstellen
# 1) Scheinerstellung mit Code
Mockito bietet mehrere überladene Versionen von Mockito. Mocks-Methode und ermöglicht das Erstellen von Mocks für Abhängigkeiten.
Syntax:
Mockito.mock(Class classToMock)
Beispiel:
Angenommen, der Klassenname lautet DiscountCalculator, um ein Modell zu erstellen:
DiscountCalculator mockedDiscountCalculator = Mockito.mock(DiscountCalculator.class)
Es ist wichtig zu beachten, dass Mock sowohl für die Schnittstelle als auch für eine konkrete Klasse erstellt werden kann.
Wenn ein Objekt verspottet wird, geben alle Methoden standardmäßig null zurück, es sei denn, sie werden gestoppt .
DiscountCalculator mockDiscountCalculator = Mockito.mock(DiscountCalculator.class);
# 2) Scheinerstellung mit Anmerkungen
Anstatt mit der statischen 'Mock' -Methode der Mockito-Bibliothek zu verspotten, bietet es auch eine Kurzform zum Erstellen von Mocks mit der Annotation '@Mock'.
Der größte Vorteil dieses Ansatzes besteht darin, dass er einfach ist und die Kombination von Deklaration und im Wesentlichen Initialisierung ermöglicht. Dies macht die Tests auch lesbarer und vermeidet die wiederholte Initialisierung von Mocks, wenn derselbe Mock an mehreren Stellen verwendet wird.
Um die Mock-Initialisierung durch diesen Ansatz sicherzustellen, müssen wir für die zu testende Klasse 'MockitoAnnotations.initMocks (this)' aufrufen. Dies ist der ideale Kandidat für die Junit-Methode 'beforeEach', mit der sichergestellt wird, dass Mocks jedes Mal initialisiert werden, wenn ein Test von dieser Klasse ausgeführt wird.
Syntax:
@Mock private transient DiscountCalculator mockedDiscountCalculator;
Spione erstellen
Ähnlich wie bei Mocks können Spione auch auf zwei Arten erstellt werden:
# 1) Spionageerstellung mit Code
Mockito.spy ist die statische Methode, mit der ein Spionageobjekt / Wrapper um die reale Objektinstanz erstellt wird.
Syntax:
wie man Elemente in ein Array Java hinzufügt
private transient ItemService itemService = new ItemServiceImpl() private transient ItemService spiedItemService = Mockito.spy(itemService);
# 2) Spionageerstellung mit Anmerkungen
Ähnlich wie bei Mock können Spies mithilfe der Annotation @Spy erstellt werden.
Auch für die Spy-Initialisierung müssen Sie sicherstellen, dass MockitoAnnotations.initMocks (this) aufgerufen wird, bevor der Spy im eigentlichen Test verwendet wird, damit der Spy initialisiert wird.
Syntax:
@Spy private transient ItemService spiedItemService = new ItemServiceImpl();
Wie injiziere ich verspottete Abhängigkeiten für die zu testende Klasse / das zu testende Objekt?
Wenn wir ein Scheinobjekt der zu testenden Klasse mit den anderen verspotteten Abhängigkeiten erstellen möchten, können wir die Annotation @InjectMocks verwenden.
Dies bedeutet im Wesentlichen, dass alle mit @ Mock- (oder @ Spy-) Annotationen gekennzeichneten Objekte als Auftragnehmer- oder Eigenschaftsinjektion in die Klasse Object eingefügt werden und anschließend Interaktionen für das endgültige verspottete Objekt überprüft werden können.
Wiederum ist @InjectMocks eine Abkürzung gegen das Erstellen eines neuen Objekts der Klasse und stellt verspottete Objekte der Abhängigkeiten bereit.
Lassen Sie uns dies anhand eines Beispiels verstehen:
Angenommen, es gibt eine Klasse PriceCalculator, die DiscountCalculator und UserService als Abhängigkeiten enthält, die über Konstruktor- oder Eigenschaftsfelder eingefügt werden.
Um die Mocked-Implementierung für die Preisrechnerklasse zu erstellen, können wir zwei Ansätze verwenden:
# 1) Erstellen eine neue Instanz von PriceCalculator und injizieren verspottete Abhängigkeiten
@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) Erstellen Eine verspottete Instanz von PriceCalculator, die Abhängigkeiten über die Annotation @InjectMocks einfügt
@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);
Die InjectMocks-Annotation versucht tatsächlich, verspottete Abhängigkeiten mithilfe eines der folgenden Ansätze einzufügen:
- Konstruktorbasierte Injektion - Verwendet den Konstruktor für die zu testende Klasse.
- Setter Methoden basiert - Wenn kein Konstruktor vorhanden ist, versucht Mockito, mithilfe von Eigenschaftssetzern zu injizieren.
- Feld basiert - Wenn die oben genannten 2 nicht verfügbar sind, wird direkt versucht, über Felder zu injizieren.
Tipps
# 1) Einrichten verschiedener Stubs für verschiedene Aufrufe derselben Methode:
Wenn eine Stubbed-Methode innerhalb der zu testenden Methode mehrmals aufgerufen wird (oder sich die Stubbed-Methode in der Schleife befindet und Sie jedes Mal eine andere Ausgabe zurückgeben möchten), können Sie Mock so einrichten, dass jedes Mal eine andere Stubbed-Antwort zurückgegeben wird.
Zum Beispiel: Angenommen, Sie möchten ItemService Um ein anderes Element für 3 aufeinanderfolgende Aufrufe zurückzugeben und Elemente in Ihrer zu testenden Methode als Element1, Element2 und Element3 deklariert zu haben, können Sie diese einfach für 3 aufeinanderfolgende Aufrufe mit dem folgenden Code zurückgeben:
@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 }
#zwei) Ausnahme durch Schein werfen: Dies ist ein sehr häufiges Szenario, wenn Sie einen Downstream / eine Abhängigkeit testen / überprüfen möchten, die eine Ausnahme auslöst, und das Verhalten des zu testenden Systems überprüfen möchten. Um jedoch eine Ausnahme von Mock auszulösen, müssen Sie stub mit thenThrow einrichten.
@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 }
Lassen Sie sich bei Spielen wie anyInt () und anyString () nicht einschüchtern, da diese in den kommenden Artikeln behandelt werden. Im Wesentlichen bieten sie Ihnen jedoch nur die Flexibilität, einen beliebigen Integer- bzw. String-Wert ohne bestimmte Funktionsargumente bereitzustellen.
Codebeispiele - Spies & Mocks
Wie bereits erwähnt, sind sowohl Spies als auch Mocks die Art von Testdoppel und haben ihre eigenen Verwendungen.
Während Spione zum Testen von Legacy-Anwendungen nützlich sind (und wo Mocks nicht möglich sind), genügen Mocks für alle anderen gut geschriebenen testbaren Methoden / Klassen den meisten Anforderungen an Unit-Tests.
Für das gleiche Beispiel: Schreiben wir einen Test mit Mocks for PriceCalculator -> berechnePreismethode (Die Methode berechnet itemPrice abzüglich der anwendbaren Rabatte)
Die PriceCalculator-Klasse und die zu testende Methode berechnePreis sehen wie folgt aus:
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; } }
Schreiben wir nun einen positiven Test für diese Methode.
Wir werden den UserService und den Item-Service wie unten beschrieben stubben:
- UserService gibt CustomerProfile immer mit dem auf 2 festgelegten LoyaltyDiscountPercentage zurück.
- ItemService gibt immer einen Artikel mit dem Basispreis von 100 und dem anwendbaren Rabatt von 5 zurück.
- Mit den oben genannten Werten beträgt der erwartete Preis, der von der zu testenden Methode zurückgegeben wird, 93 $.
Hier ist der Code zum Testen:
@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); }
Wie Sie sehen können, behaupten wir im obigen Test, dass der von der Methode zurückgegebene tatsächliche Preis dem erwarteten Preis entspricht, d. H. 93,00.
bestes kostenloses Windows 10-Bereinigungsprogramm
Schreiben wir jetzt einen Test mit Spy.
Wir werden den ItemService ausspionieren und die ItemService-Implementierung so codieren, dass sie immer einen Artikel mit dem basePrice 200 und dem anwendbaren Rabatt von 10,00% zurückgibt (der Rest des Mock-Setups bleibt gleich), wenn er mit skuCode von 2367 aufgerufen wird.
@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);
Nun sehen wir uns eine an Beispiel einer Ausnahme, die von ItemService ausgelöst wurde, da die verfügbare Artikelmenge 0 war. Wir werden Mock einrichten, um eine Ausnahme auszulösen.
@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)); }
Mit den obigen Beispielen habe ich versucht, das Konzept von Mocks & Spies zu erklären und zu erklären, wie sie kombiniert werden können, um effektive und nützliche Unit-Tests zu erstellen.
Es kann mehrere Kombinationen dieser Techniken geben, um eine Reihe von Tests zu erhalten, die die Abdeckung der zu testenden Methode verbessern, wodurch ein hohes Maß an Vertrauen in den Code sichergestellt und der Code widerstandsfähiger gegen Regressionsfehler gemacht wird.
Quellcode
Schnittstellen
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); }
Schnittstellenimplementierungen
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) { } }
Modelle
Kundenprofil
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 im 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; } }
Unit Tests - 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)); } }
In unserem nächsten Tutorial werden verschiedene Arten von Matchern erläutert, die von Mockito bereitgestellt werden.
PREV Tutorial | NÄCHSTES Tutorial
Literatur-Empfehlungen
- Verschiedene Arten von Matchern von Mockito
- Mockito Tutorial: Mockito Framework zum Verspotten im Unit Testing
- Erstellen von Epochentests mit epochs Studio for Eclipse
- Python DateTime Tutorial mit Beispielen
- Befehl in Unix mit Beispielen ausschneiden
- Unix Cat-Befehlssyntax, Optionen mit Beispielen
- Verwendung des Cursors in MongoDB mit Beispielen
- Ls-Befehl unter Unix mit Beispielen