Desmistificando testes de unidade no Vue

Marcos Vinicios da Silva Neves
Marcos Vinicios da Silva Neves

Compartilhe

Nesse artigo vamos aprender a identificar o que precisamos testar nas aplicações VueJS.

Num cenário onde existe integração contínua e deploy contínuo, testes são muito necessários. Afinal, precisamos garantir que o código que estamos entregando funcione como o esperado e sem efeitos colaterais. Não existe um consenso sobre teste de software, alguns querem falar sobre cobrir todas as linhas de código (em inglês, coverage)... ou seja, testar 100% do que foi escrito. Enquanto outros não estão preocupados com cobertura, mas sim em garantir o comportamento. De um jeito ou de outro, o ponto em comum é sempre testar a menor parte da aplicação. E é aí que entram os testes de unidade.

Quase ninguém voaria num avião que não fosse testado, certo? E muito embora um erro cometido em nossas aplicações não seja tão grave quanto uma falha numa aeronave, mas podemos causar grandes prejuízos e isso não é bom. Teste de software é um conteúdo muito vasto, então vamos discutir neste artigo uma visão mais pragmática sobre testes de unidade (teste de unidade - unit test - é um método onde testamos as menores porções de nossa aplicação).

Uma dúvida frequente para as pessoas que começam a escrever testes para seus componentes é: "Ok, o que eu deveria testar?". No dia a dia, trabalhamos com pessoas que testam demais ou de menos e nenhum extremo costuma ser saudável. Ao testar demais (over testing) você acaba testando além dos seus componentes, como APIs do próprio Vue. E testar de menos (under testing) não vai te dar a confiança necessária para rodar uma pipeline de publicação de forma automática. Vamos, então, seguir as boas práticas e testar com qualidade e somente o que é necessário.

Para testes de componentes de UI (user interface), o mais indicado é a escrita de teste baseado nos contratos dos componentes - a API pública. E o próprio componente em si é tratado como uma caixa preta. A ideia é testar que, dado uma entrada - input - (uma prop alterada ou uma interação do usuário), temos a saída - output - esperada. Simples assim. Vamos imaginar um componente que controla a quantidade de um item num carrinho de compras:

<template>
  <div>
    <label for="qtd">Quantidade</label>
    <input type="number" v-model="quantidade">
    <button id="incrementar" @click="incrementar">+ 1</button>
    <button id="decrementar" @click="decrementar">- 1</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      quantidade: 0,
    };
  },
  methods: {
    incrementar() {
      this.quantidade += 1;
    },
    decrementar() {
      this.quantidade -= 1;
    },
  },
};
</script>

Seguindo o indicado pelo Vue Test Utils, vamos escrever testes para cobrir os cenários de interação com o usuário:

  • dado o cenário inicial, o usuário clica no botão "+"

Nesse teste, vamos montar o componente, localizar e clicar no botão de incremento, obter o value do input e testar se é o valor esperado:

import { mount } from '@vue/test-utils';
import QuantidadeCarrinho from '@/components/QuantidadeCarrinho.vue';

describe('QuantidadeCarrinho.vue', () => {
  test('aumenta a quantidade em um ao clicar no btn +', async () => {
    const wrapper = mount(QuantidadeCarrinho);
    await wrapper.find('#incrementar').trigger('click');
    const input = wrapper.find('input');
    const quantidade = input.element.value;
    expect(quantidade).toBe('1');
  });
});

Perfeito! Exatamente como o esperado. Dado que o valor inicial é zero, pedimos um único incremento e ele passa a ser um. Vamos para o próximo cenário.

  • dado o cenário inicial, o usuário altera o valor do input e em seguida clica no botão "+"
  test('após definição de um valor no input, aumenta a quantidade em um ao clicar no btn +', async () => {
    const wrapper = mount(QuantidadeCarrinho);
    const input = wrapper.find('input');
    input.setValue('5');
    await wrapper.find('#incrementar').trigger('click');
    const quantidade = input.element.value;
    expect(quantidade).toBe('6');
  });

Com o nosso segundo teste escrito, vamos rodar e ver se tudo funciona conforme o esperado:

Opa! Esse não é o comportamento esperado. Analisando nosso componente, é fácil descobrir o motivo. No método incrementar, ele faz:

    incrementar() {
      this.quantidade += 1;
    },

Quando o usuário altera o valor via input, ele passa a ser uma string. E então o JS concatena a string ao invés de fazer a soma. Vamos fazer uma pequena refatoração - Transformar a quantidade num valor inteiro antes de fazer o incremento:

    incrementar() {
      this.quantidade = parseInt(this.quantidade) + 1;
    },

Agora, de volta ao terminal para rodar os testes:

Tudo funciona conforme o esperado. Esse tipo de ajustes e pequenas refatorações são comuns no dia a dia do desenvolvimento de testes. Existe, inclusive, uma cultura de desenvolvimento guiado por testes, chamado TDD (test driven development).

Olhando a documentação oficial , uma das bibliotecas recomendadas para testes é o Vue Test Utils. O próprio Vue Test Utils não recomenda uma abordagem baseada em cobertura de linha, ou seja, garantir que cada linha de código seja testada. Ao seguir com a abordagem de cobertura de linhas de código, focamos nas implementações internas e isso pode gerar testes frágeis. É por isso que preferimos uma abordagem orientada a contratos e interfaces públicas, tratando o componente como uma caixa preta, mas garantindo que o comportamento seja exatamente o desejado, dada às interações com o usuário ou outros componentes e eventos.

Por baixo dos panos, utilizamos o Jest e o próprio Vue Test Utils. Se gostou desse conteúdo e quer saber mais sobre, aqui na Alura temos uma Formação VueJS onde vamos nos aprofundar ainda mais em todo o ecossistema do VueJS.

Marcos Vinicios da Silva Neves
Marcos Vinicios da Silva Neves

Marcos é arquiteto de software, envolvido na arquitetura, design e implementação de microsserviços, micro frontends e sistemas distribuídos. Tem experiência significativas em aplicativos, integração e arquitetura corporativa. É Engenheiro de Software pela UNESA e Arquiteto de Software pela PUC Minas.

Veja outros artigos sobre Front-end