Exemplo de Teste Unitário com JUnit + Mockito
Continuando o Exemplo de Teste Unitário com JUnit, iremos adicionar novas funcionalidades ao módulo Carrinho de compras. Agora, ao adicionar um novo item no carrinho, o módulo deve consultar outro módulo chamado de Estoque.
Esse novo módulo não pode fazer parte do nosso teste unitário para o módulo Carrinho de Compras, uma vez que um erro nos testes unitários não poderia dar um feedback preciso sobre qual classe gerou uma falha. Sendo assim, vamos criar um Mock, como uma cópia do módulo real, ou seja, o nosso módulo Carrinho de Compras durante os testes vai se comunicar com um objeto pré-programado, que tem estado fixo e válido, e não vai interferir nos resultados dos testes.
Para criar os Mocks, vamos usar uma biblioteca chamada Mockito. Os dados para inclusão dela como dependência de um projeto utilizando Maven estão descritas abaixo.
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.9.5</version>
<scope>test</scope>
</dependency>
Refatorando e adicionando o módulo de Estoque
Nossa classe Carrinho
mostrada anteriormente passa a sofrer alterações para suportar a integração com o novo módulo de Estoque. Ao adicionar um produto, verifica se o mesmo está em estoque, e caso não esteja, lança uma exceção do tipo ForaEstoqueException
. Perceba que nesse momento, os detalhes de como Estoque
está implementado não importam, estamos testando apenas o componente Carrinho
.
public class Carrinho {
private List<Produto> produtos = new LinkedList<Produto>();
private Estoque estoque;
public Carrinho() {
this.estoque = new EstoqueImpl();
}
public Carrinho(Estoque estoque) {
this.estoque = estoque;
}
public void adicionarItem(Produto produto) throws ForaEstoqueException {
if (!estoque.temItem(produto))
throw new ProdutoForaEstoqueException("Produto fora de estoque");
this.produtos.add(produto);
if (produtos.size() >= 4) {
Collections.sort(this.produtos);
}
}
// Restante dos métodos sem alterações
}
Vamos alterar nossa classe de teste para criar os mocks e "injetar" os objetos falsos dentro de um objeto do tipo Carrinho, para que seu comportamento seja previsível e controlado. O Mockito disponibiliza uma anotação @Mock
para anotar atributos que desejamos que sejam Mocks. Em seguida, inicializamos esses mocks por meio do método estático MockitoAnnotations.initMocks()
que recebe uma classe onde estão os atributos anotados com @Mock
, nesse caso, a própria classe (this
).
public class CarrinhoTest {
@Mock
private Estoque estoque;
private Carrinho carrinho;
@Before
public void prepararCenario() {
// Preparar o cenário
MockitoAnnotations.initMocks(this);
this.carrinho = new Carrinho(estoque);
}
...
Em seguida, vamos configurar qual vai ser a resposta do Mock, dado um estímulo específico a seus métodos. Criaremos um teste para visualizar se a nossa classe Carrinho
está funcionando em um cenário hipotético (com mock) de que o item sempre estará em estoque.
Nesse exemplo, estamos configurando o objeto estoque
para quando (when
) o método temItem
for invocado, recebendo qualquer (any
) objeto do tipo Produto
, ele retorne true
. A resposta programada aqui serve para que os detalhes de estoque
não influenciem no teste de Carrinho
.
@Test
public void adicionarItemComEstoque() throws ProdutoForaEstoqueException {
Mockito.when(estoque.temItem(Mockito.any(Produto.class))).thenReturn(true);
// Realizar o estímulo ao sistema
Produto produto1 = new Produto(1, "Mouse", 50L);
carrinho.adicionarItem(produto1);
Assert.assertEquals(1, carrinho.getQtdeItens());
}
Podemos também simular cenários onde haverão exceções no fluxo da aplicação. No exemplo abaixo, definimos que o estoque
sempre vai retornar false
para o mesmo cenário anterior. Esse teste tem como objetivo verificar se o lançamento da exceção de fato está ocorrendo, uma vez que ela é responsabilidade de Carrinho e está implementado lá. Para verificar se uma exceção foi lançada, usamos o atributo expected
dentro da anotação @Test
.
@Test(expected = ProdutoForaEstoqueException.class)
public void adicionarItemSemEstoque() throws ProdutoForaEstoqueException {
Mockito.when(estoque.temItem(Mockito.any(Produto.class))).thenReturn(false);
// Realizar o estímulo ao sistema
Produto produto1 = new Produto(1, "Mouse", 50L);
carrinho.adicionarItem(produto1); // A exceção é lançada aqui
// Por isso não há necessidade de Assert
}