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
}

As possibilidades de uso dos mocks são enormes, e não vale a pena explorar todos aqui, uma vez que eles dependem mais do contexto que você irá testar e quais tipos de cenários você deseja explorar. Vale a pena dar uma olhada nesses exemplos, para os que programam dentro do ecossistema Java.

Os demais frameworks/bibliotecas de teste, mesmo no ecossistema Javascript utilizam da mesma idéia. O que é preciso ficar fixado ao final dessa seção é o porquê de utilizar mocks e como isso ajuda a garantir que os testes estão isolados.

Last updated

Was this helpful?