creating mocks spies mockito with code examples
Tutorial de Mockito Spy e Mocks:
Nisso Série Mockito Tutorial , nosso tutorial anterior nos deu um Introdução ao Mockito Framework . Neste tutorial, aprenderemos o conceito de Mocks e Spies no Mockito.
O que são Mocks e Spies?
Mocks e Spies são os tipos de duplas de teste, que são úteis para escrever testes de unidade.
Mocks são um substituto completo para a dependência e podem ser programados para retornar a saída especificada sempre que um método no mock for chamado. Mockito fornece uma implementação padrão para todos os métodos de um mock.
O que você aprenderá:
- O que são espiões?
- Criação de simulações
- Criação de espiões
- Como injetar dependências simuladas para a classe / objeto em teste?
- dicas e truques
- Exemplos de código - espiões e simulações
- Código fonte
- Leitura recomendada
O que são espiões?
Os espiões são essencialmente um invólucro em uma instância real da dependência simulada. O que isso significa é que ele requer uma nova instância do objeto ou dependência e, em seguida, adiciona um wrapper do objeto simulado sobre ele. Por padrão, os Spies chamam métodos reais do Objeto, a menos que sejam fragmentados.
Os espiões fornecem certos poderes adicionais, como quais argumentos foram fornecidos para a chamada do método, se o método real foi chamado etc.
Resumindo, para espiões:
- A instância real do objeto é necessária.
- Spies oferece flexibilidade para stub alguns (ou todos) métodos do objeto espiado. Naquela época, o espião é essencialmente chamado ou referido a um objeto parcialmente simulado ou fragmentado.
- As interações chamadas em um objeto espiado podem ser rastreadas para verificação.
Em geral, Spies não são usados com muita frequência, mas podem ser úteis para aplicativos legados de teste de unidade onde as dependências não podem ser totalmente simuladas.
Para toda a descrição do Mock and Spy, estamos nos referindo a uma classe / objeto fictício chamado ‘DiscountCalculator’, que queremos simular / espiar.
Possui alguns métodos, conforme mostrado abaixo:
CalculeDiscount - Calcula o preço com desconto de um determinado produto.
getDiscountLimit - Busca o limite superior de desconto para o produto.
Criação de simulações
# 1) Criação de simulação com código
Mockito oferece várias versões sobrecarregadas de Mockito. Método de simulação e permite a criação de simulações para dependências.
Sintaxe:
Mockito.mock(Class classToMock)
Exemplo:
Suponha que o nome da classe seja DiscountCalculator, para criar uma simulação no código:
DiscountCalculator mockedDiscountCalculator = Mockito.mock(DiscountCalculator.class)
É importante notar que o Mock pode ser criado tanto para interface quanto para uma classe concreta.
Quando um objeto é simulado, a menos que tenha sido feito um stub, todos os métodos retornam nulo por padrão .
DiscountCalculator mockDiscountCalculator = Mockito.mock(DiscountCalculator.class);
# 2) Criação de simulação com anotações
Em vez de simular usando o método estático 'simulado' da biblioteca Mockito, ele também fornece uma forma abreviada de criar simulações usando a anotação '@Mock'.
A maior vantagem desta abordagem é que ela é simples e permite combinar declaração e essencialmente inicialização. Também torna os testes mais legíveis e evita a inicialização repetida de mocks quando o mesmo mock está sendo usado em vários lugares.
A fim de garantir a inicialização do Mock por meio dessa abordagem, é necessário chamar ‘MockitoAnnotations.initMocks (this)’ para a classe em teste. Este é o candidato ideal para fazer parte do método ‘beforeEach’ do Junit, que garante que as simulações sejam inicializadas cada vez que um teste dessa classe for executado.
Sintaxe:
@Mock private transient DiscountCalculator mockedDiscountCalculator;
Criação de espiões
Semelhante aos Mocks, Spies também podem ser criados de 2 maneiras:
# 1) Criação de espião com código
Mockito.spy é o método estático usado para criar um objeto / invólucro 'espião' em torno da instância do objeto real.
Sintaxe:
qual é o meu login e senha do roteador
private transient ItemService itemService = new ItemServiceImpl() private transient ItemService spiedItemService = Mockito.spy(itemService);
# 2) Criação de espiões com anotações
Semelhante ao Mock, Spies podem ser criados usando a anotação @Spy.
Para a inicialização do Spy também, você deve garantir que MockitoAnnotations.initMocks (this) seja chamado antes que o Spy seja usado no teste real para que o spy seja inicializado.
Sintaxe:
@Spy private transient ItemService spiedItemService = new ItemServiceImpl();
Como injetar dependências simuladas para a classe / objeto em teste?
Quando queremos criar um objeto fictício da classe em teste com as outras dependências fictícias, podemos usar a anotação @InjectMocks.
O que isso essencialmente faz é que todos os objetos marcados com anotações @Mock (ou @Spy) são injetados como Contractor ou injeção de propriedade na classe Object e então as interações podem ser verificadas no objeto Mocked final.
Novamente, nem é preciso mencionar, @InjectMocks é um atalho contra a criação de um novo Object da classe e fornece objetos simulados das dependências.
Vamos entender isso com um exemplo:
Suponha que haja uma classe PriceCalculator, que tem DiscountCalculator e UserService como dependências que são injetadas por meio dos campos Construtor ou Propriedade.
Portanto, a fim de criar a implementação simulada para a classe Calculadora de preço, podemos usar 2 abordagens:
# 1) Crie uma nova instância de PriceCalculator e injetar dependências simuladas
@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) Crie uma instância simulada de PriceCalculator e injetar dependências por meio da anotação @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);
Na verdade, a anotação InjectMocks tenta injetar dependências simuladas usando uma das abordagens abaixo:
- Injeção Baseada em Construtor - Utiliza Construtor para a classe em teste.
- Métodos de setter baseados - Quando um Construtor não está lá, Mockito tenta injetar usando configuradores de propriedade.
- Baseado em campo - Quando os 2 acima não estiverem disponíveis, ele tenta injetar diretamente através dos campos.
dicas e truques
# 1) Configurar diferentes stubs para diferentes chamadas do mesmo método:
Quando um método em stub é chamado várias vezes dentro do método em teste (ou o método em stub está no loop e você deseja retornar uma saída diferente a cada vez), então você pode configurar o Mock para retornar uma resposta em stub diferente a cada vez.
Por exemplo: Suponha que você queira ItemService para retornar um item diferente para 3 chamadas consecutivas e você tiver itens declarados em seu método em testes como Item1, Item2 e Item3, então você pode simplesmente retorná-los para 3 chamadas consecutivas usando o código abaixo:
@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 }
#dois) Jogando Exceção através do Mock: Este é um cenário muito comum quando você deseja testar / verificar um downstream / dependência lançando uma exceção e verificar o comportamento do sistema em teste. No entanto, para lançar uma exceção pelo Mock, você precisará configurar o stub usando o 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 }
Para correspondências como anyInt () e anyString (), não se deixe intimidar, pois serão abordadas nos próximos artigos. Mas, em essência, eles apenas fornecem a flexibilidade de fornecer qualquer valor Integer e String, respectivamente, sem nenhum argumento de função específico.
Exemplos de código - espiões e simulações
Conforme discutido anteriormente, tanto Spies quanto Mocks são o tipo de teste duplo e têm seus próprios usos.
Enquanto spies são úteis para testar aplicativos legados (e onde mocks não são possíveis), para todos os outros métodos / classes testáveis bem escritos, Mocks é suficiente para a maioria das necessidades de teste de Unidade.
Para o mesmo exemplo: Vamos escrever um teste usando Mocks para PriceCalculator -> método describePrice (O método calcula itemPrice menos os descontos aplicáveis)
A classe PriceCalculator e o método em teste describePrice tem a seguinte aparência:
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; } }
Agora vamos escrever um teste positivo para este método.
Vamos criar um stub userService e item service conforme mencionado abaixo:
- UserService sempre retornará CustomerProfile com loyaltyDiscountPercentage definido como 2.
- ItemService sempre retornará um Item com basePrice de 100 e applyDiscount de 5.
- Com os valores acima, o valor esperado retornado pelo método em teste resulta em 93 $.
Aqui está o código para teste:
@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); }
Como você pode ver, no teste acima - estamos afirmando que o preço real retornado pelo método é igual ao preço esperado, ou seja, 93,00.
Agora, vamos escrever um teste usando o Spy.
Espiaremos o ItemService e codificaremos a implementação ItemService de uma forma que sempre retorne um item com basePrice 200 e optionalDiscount de 10,00% (o resto da configuração simulada permanece a mesma) sempre que for chamado com skuCode de 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);
Agora, vamos ver um Exemplo de uma exceção lançada por ItemService, pois a quantidade de item disponível era 0. Configuraremos o mock para lançar uma exceção.
@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)); }
Com os exemplos acima, tentei explicar o conceito de Mocks & Spies e como eles podem ser combinados para criar testes de unidade eficazes e úteis.
Pode haver várias combinações dessas técnicas para obter um conjunto de testes que aumentam a cobertura do método em teste, garantindo assim um grande nível de confiança no código e tornando o código mais resistente a bugs de regressão.
Código fonte
Interfaces
DiscountCalculator
public interface DiscountCalculator { double calculateDiscount(ItemSku itemSku, double markedPrice); void calculateProfitability(ItemSku itemSku, CustomerProfile customerProfile); }
ItemService
diferença entre cenário de teste e caso de teste
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); }
Implementações de interface
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) { } }
Modelos
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 em teste - 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; } }
Testes de unidade - 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)); } }
Diferentes tipos de Matchers fornecidos pelo Mockito são explicados em nosso próximo tutorial.
PREV Tutorial | PRÓXIMO Tutorial
Leitura recomendada
- Diferentes tipos de matchers fornecidos por Mockito
- Tutorial Mockito: Framework Mockito para simulação em testes de unidade
- Criação de testes de épocas usando o epochs Studio for Eclipse
- Tutorial Python DateTime com exemplos
- Cortar comando no Unix com exemplos
- Sintaxe de comando Unix Cat, opções com exemplos
- Uso do cursor no MongoDB com exemplos
- Comando Ls no Unix com exemplos