Testes Unitários em JavaScript: Um Guia Completo para Iniciantes

Giovanna Moeller
6 min readJun 30, 2023

--

O desenvolvimento de software exige uma estratégia de testes para garantir que as funcionalidades funcionem conforme o esperado e para evitar a introdução de novos bugs ao realizar alterações.

Entre os tipos de testes que você pode escrever para o seu código, os testes unitários são fundamentais e desempenham um papel crucial na garantia da qualidade do software. Este artigo fornecerá uma visão detalhada dos testes unitários em JavaScript, incluindo exemplos de código para demonstrar os conceitos discutidos.

O que são testes unitários?

Os testes unitários são um tipo de teste de software que verifica a menor unidade de código testável em um programa. Normalmente, isso é uma função que pode ser isolada do restante do código. Esses testes ajudam os desenvolvedores a garantir que o código funcione corretamente à medida que o sistema evolui.

Por que os testes unitários são importantes?

Os testes unitários trazem uma série de benefícios, incluindo:

  • Detectar e corrigir bugs no início do ciclo de desenvolvimento;
  • Melhorar o design do código, promovendo código pequeno e modular;
  • Facilitar as refatorações, já que você pode alterar o código e rapidamente verificar se tudo ainda funciona como esperado;
  • Servir como documentação, demonstrando como um pedaço de código deve funcionar.

Escrevendo testes unitários no Javascript

Existem várias bibliotecas disponíveis para escrever testes unitários em JavaScript, incluindo Mocha, Jasmine e Jest. Para fins deste artigo, vamos usar o Jest, um dos mais conhecidos e utilizados atualmente.

Instalando o Jest

Você pode instalar o Jest utilizando o NPM (Node Package Manager).

NPM é um gerenciador de pacotes para a plataforma Node.js. Ele vem incluído com a instalação do Node.js e é uma das ferramentas mais amplamente usadas no ecossistema JavaScript.

Para instalar o Jest no seu projeto, utilize este comando:

npm install --save-dev jest

Utilizamos a opção --save-dev pois testes são necessários apenas para o desenvolvimento do nosso projeto, não sendo necessário incluí-los em ambiente de produção.

Escrevendo o nosso primeiro teste unitário

Digamos que temos um arquivo chamado soma.js com a seguinte função em JavaScript que queremos testar:

function soma(a, b) {
return a + b
}

module.exports = soma

Para escrever um teste para essa função, criamos um novo arquivo com o mesmo nome, mas com a extensão .test.js. Portanto, seria nomeado como soma.test.js.

Então, vamos começar escrevendo o nosso teste:

const soma = require('./soma'); // importando a função de outro arquivo

test('soma 1 + 2 para resultar em 3', function() {
expect(soma(1, 2)).toBe(3);
});

Aqui, test é uma função global fornecida pelo Jest para definir um teste. O primeiro argumento é uma string que descreve o que o teste deve fazer, e o segundo argumento é uma função que contém a lógica do teste.

A função expect é usada para definir uma "afirmação" ou "expectativa". Em outras palavras, é onde você define o que é esperado que o código produza. Neste caso, estamos esperando que soma(1, 2) seja 3.

Para executar o teste, você pode adicionar o seguinte script no seu arquivo package.json:

{
"scripts": {
"test": "jest"
}
}

E então, execute o comando npm test no terminal. Caso o seu teste passe, você irá verificar uma mensagem de sucesso. Veja a imagem abaixo:

Imagem mostrando sucesso nos testes
Imagem mostrando sucesso nos testes

Isso significa que nossos testes foram concluídos com sucesso. No total, tínhamos apenas um teste e o mesmo passou nos critérios desejados.

Caso mudarmos a implementação da função soma de forma incorreta e tentarmos executar os testes novamente, eles irão falhar. Esse é um dos motivos pelo qual testes são muito importantes, garantindo sempre que nossa aplicação funcione da maneira esperada.

Outros matchers do Jest

No Jest, expect é uma função que toma um valor que você deseja testar, também conhecido como "valor recebido". Você pode encadear a função expect com "matchers" para testar o valor de maneiras diferentes. No exemplo que você viu acima, .toBe é um matcher que testa a igualdade estrita.

Existem muitos outros matchers disponíveis em Jest. Vou listar alguns dos mais comuns:

  • .toEqual(value): verifica a igualdade de valor e estrutura. Útil para verificar se dois objetos ou arrays são iguais, já que seus valores são passados por referência;
  • .not: nega o matcher que o segue
  • .toBeNull(): verifica se o valor é null;
  • .toBeUndefined(): verifica se o valor é undefined;
  • .toBeDefined(): verifica se o valor não é undefined;
  • .toBeTruthy(): verifica se o valor é truthy (isto é, se ele se comporta como true em um contexto booleano);
  • .toBeFalsy(): verifica se o valor é falsy (isto é, se ele se comporta como false em um contexto booleano);
  • .toContain(item): verifica se uma array contém um item específico;
  • .toBeCloseTo(number, numDigits?): verifica se um número é próximo de outro número, dentro de um número específico de casas decimais;
  • .toMatch(regexp | string): verifica se uma string corresponde a uma string ou expressão regular específica;
  • .toThrow(error?): verifica se uma função lança um erro quando é chamada.

Você pode ver a lista completa de matchers na documentação oficial do Jest.

Cobertura dos testes

A cobertura de testes é uma métrica útil que mostra qual porcentagem do seu código foi coberta por testes. Isso pode ajudá-lo a identificar áreas do seu código que não foram testadas e podem ser fontes potenciais de bugs. Vamos ver como você pode gerar um relatório de cobertura de testes usando Jest.

O Jest vem com um sistema de cobertura de testes integrado. Para coletar informações de cobertura ao executar seus testes, você precisa adicionar a opção --coverage ao comando jest, dessa forma:

{
"scripts": {
"test": "jest --coverage"
}
}

Em seguida, execute seus testes novamente usando npm test. Após a execução dos testes, você verá um resumo da cobertura de testes no terminal. Além disso, o Jest gera um diretório coverage na pasta do seu projeto com um relatório de cobertura de testes mais detalhado.

Você pode, inclusive, abrir essa pasta, navegar até um outro diretório chamado lconv-report e abrir o arquivo index.html. Você irá ver uma interface parecida com a da imagem abaixo, mostrando os relatórios em relação a cobertura de testes:

Relatório visual para cobertura de testes
Relatório visual para cobertura de testes

Maravilha! Temos 100% do nosso código coberto por testes. Mas, e se criarmos uma nova função dentro do nosso arquivo soma.js e não testá-la? Vamos ver o que acontece?

function soma(a, b) {
return a + b
}

function soma3(a, b, c) {
return a + b + c
}

module.exports = soma, soma3

Ao executar o comando npm test, podemos ver que o relatório já fica em tom amarelo, pois não temos mais uma cobertura de 100%. O mesmo acontece no relatório visual:

Testes sem cobertura total
Testes sem cobertura total
Testes sem cobertura total — Modelo visual
Testes sem cobertura total — Modelo visual

Inclusive, ele nos mostra quantas linhas estão sem testes, e a porcentagem total. Incrível, não?

Boas práticas para testes unitários

Vamos falar sobre algumas práticas recomendadas para desenvolvimento de testes unitários:

  1. Manter os testes pequenos e focados: Cada teste deve verificar uma única coisa e ter uma clara descrição do que está sendo testado;
  2. Testar o comportamento, não a implementação: Seu teste deve verificar o que a função faz, não como ela faz. Isso torna seus testes mais robustos e menos propensos a falhar se a implementação interna mudar;
  3. Isolar os testes: Cada teste deve ser independente dos outros. Não deve haver dependências entre os testes, para que você possa executá-los em qualquer ordem;
  4. Usar dados de testes representativos: Isso ajuda a garantir que sua função lidará corretamente com uma grande variedade de casos de entrada. Explore mais os casos de teste.

Conclusão

Os testes unitários são uma parte essencial do desenvolvimento de software e podem melhorar significativamente a qualidade do seu código. Embora possa parecer um investimento de tempo à frente, os benefícios a longo prazo em termos de detecção precoce de bugs e facilitação das refatorações tornam os testes unitários uma prática valiosa.

Obrigada por ter lido até aqui!

--

--