TDD: Como criar integration tests em Node.js com Jest

Este tutorial tem versão em videoaula no meu curso Web FullStack JS!

Recentemente escrevi um tutorial de como utilizar o pacote Jest para escrevermos testes unitários automatizados em aplicações Node.js. No tutorial de hoje, vamos falar de testes de integração.

Chama-se de teste de integração aqueles testes mais amplos em que um teste acaba testando internamente diversas outras funções e dependendo de recursos externos muitas vezes. Por exemplo, testar se uma chamada GET a uma API está funcionando não é exatamente um teste de unidade considerando que estaremos neste caso testando a chamada/request HTTP da aplicação, o roteamento, a função em si e o retorno/resposta HTTP.

Geralmente testes de integração são mais complicados de automatizar, principalmente quando envolvem interface de usuário ou elementos complexos de infraestrutura, como bancos de dados, embora não seja impossível.

Uma técnica muito comum para abstrair questões de infraestrutura são o uso de mocking. Mocking é o ato de falsificar algum elemento externo à sua aplicação, geralmente de infraestrutura, como se ele nunca falhasse. Por exemplo, uma função que acessa o banco de dados, em um teste que não importa o acesso ao banco, poderia ser mockada para retornar sempre o valor que queremos no teste, pois o que queremos testar é o uso do valor, não o acesso ao banco.

Existem diversos módulos no Node.js que facilitam esta tarefa de mockar objetos e recursos externos, mas particularmente não sou muito fã desta técnica. Em algumas ocasiões é a única saída (alguém aí falou de integração com bancos e financeiras?) mas geralmente prefiro ter ambientes de testes ou simulá-los de maneira mais próxima à realidade.

Com o que vimos no tutorial anterior já é possível escrever testes de integração apenas com Node.js, Jest e algum client HTTP famoso como Axios. Por exemplo:

No entanto, testar dessa forma exige que você mantenha a sua aplicação ou web API Node.js sempre up na sua máquina ou em uma máquina da rede, o que pode gerar problemas eventualmente.

Uma dica é não fazermos desta forma, mas sim instanciando a nossa aplicação Express e chamando-a diretamente com módulos HTTP que aceitam inversão de dependência, como o Supertest.

Curso Node.js e MongoDB

Testando requisições com Supertest

Basicamente o Supertest é um módulo que forja requisições visando testar webservers em Node.js e verifica o retorno das mesmas para automatizar testes deste tipo de infraestrutura, principalmente web APIs.

Mas antes de usar o supertest, vamos criar uma web API Node.js bem simples, para usarmos ela em nossos testes. Serei muito breve aqui pois foge do escopo do tutorial ensinar a programar web APIs em Node.js com Express.

Resumidamente, criamos uma web API usando Express que escuta na porta 3000 esperando por requisições GET /aplicarDesconto passando no path o valor e o desconto a ser aplicado. Internamente pega-se esses dois parâmetros passados no path, converte-os para inteiro e usa-se a função que criamos anteriormente (e cujo módulo index.js carregamos no topo deste arquivo) para calcular o valor descontado, retornando-o em um JSON na resposta.

Atenção ao if que inicia o servidor somente no caso do require.main for igual a module, pois isso evita que o servidor fique ‘pendurado’ mais tarde, durante os testes.

Se você executar esse arquivo com o comando ‘node app’ verá que ele funciona perfeitamente, depois que você instalar a dependência do Express, é claro.

E também sugiro alterar o script de start no seu package.json para iniciar a aplicação mais facilmente.

Repita o procedimento para instalar e configurar o Jest que vimos no tutorial anterior, ok? Partiremos do seu Jest já configurado no projeto, incluindo o collectCoverage como true no jest.config.js.

Para usar o Supertest, devemos primeiro instalar sua dependência de desenvolvimento em nossa aplicação:

Já aprendemos antes como criar os testes de unidade dos nossos módulos, garantindo que nossas funções mais elementares estão ok, lembra? Vamos usar o mesmo index.js nessa web API, que reproduzo abaixo para que você possa criá-lo na raiz da sua web API de desconto.

Até esse nome ficou bem ruinzinho, então altere à vontade, apenas não esqueça de editar também o require dele no app.js.

Os testes de unidade da função aplicarDesconto que fizemos garantem que ela está funcionando, o que já garante boa parte do funcionamento da nossa API. Agora vamos criar testes de integração que garantam o funcionamento completo dessa chamada.

Para isso, vamos criar um arquivo app.test.js, que conterá todos os testes do app.js. Comece esse arquivo carregando o módulo supertest e o app:

Agora, escreva um teste usando Jest, que nem já fizemos antes, mas usaremos o supertest dentro dele para forjar a requisição e ler a resposta, visando uma asserção completa, que integre todo o uso da API:

Quando chamamos a função supertest devemos passar para ela o nosso app. A função get define a requisição que faremos, enquanto que as funções expect definem características que indicam que nosso test foi bem sucedido, analisando por exemplo o body e o HTTP status code.

O resultado de rodar com “npm test” é este:

Note que tem linhas de código não cobertas por testes, o que impediu que a nossa cobertura ficasse em 100%. Para atingir os 100%, devemos escrever novos testes que passem pelas linhas descobertas, geralmente adicionando cenários novos às mesmas rotas e funcionalidades.

Para sistemas novos, a recomendação é buscar no mínimo estar acima de 60% e idealmente acima de 80%. Para sistemas legado, que não tinham testes, o ideal é que toda nova funcionalidade, refatoração e correção tenham testes. Assim, você vai ter uma cobertura baixa, mas pelo menos vai garantir que o que for novo código, vai ter testes.

Curso FullStack
Curso FullStack

Dicas adicionais

Dica 1: qualquer tipo de conexão que você efetue deve ser encerrada ao término dos testes, caso contrário os testes ficam ‘pendurados’ aguardam o encerramento de todos recursos.

O if que coloquei antes do listen no app.js garante que o servidor não fique rodando eternamente. No caso de um banco de dados, o último teste deve ser sempre o de encerramento da conexão. E assim por diante.

Dica 2: os testes do Jest rodam em paralelo, ou seja, não há garantias de ordem de execução e por isso devem ser independentes. Caso você queira que eles rodem de maneira serial (um após o outro), você pode usar a flag -i na execução do Jest, como abaixo.

Isso não é recomendado por questão de performance dos testes, mas de repente pode ser a sua única alternativa.

Dica 3: alguns dos seus testes exigirão configurações prévias. Você pode fazer estas configurações antes de todos os testes ou antes de cada um deles, usando as funções beforeAll e beforeEach do Jest, respectivamente, no mesmo nível das funções de test.

Dica 4: alguns dos seus testes exigirão que você limpe recursos após a realização deles. Você pode fazer estas configurações depois de todos os testes ou antes de cada um deles, usando as funções afterAll ou afterEach do Jest, respectivamente, no mesmo nível das funções de test.

Dica 5: você pode agrupar conjuntos de testes relacionados usando a função describe, como abaixo.

Isso organiza melhor na hora de exibir o relatório dos testes e também permite que você crie beforeAll/beforeEach/afterAll/afterEach dentro do callback do describe, o que organiza melhor as configurações prévias e pós os testes.

Dica 6: alguns testes são bem chatos de fazer por serem complexos ou mesmo lentos. Para estes casos, a técnica de mocking pode ajudar bastante. Aprenda neste tutorial.

Note também que usei ali funções it dentro do describe ao invés de test. É exatamente a mesma coisa, são sinônimos. E esta foi a dica 7. 🙂

Espero que tenham gostado do tutorial e que apliquem em seus projetos!

Curtiu o post? Então clica no banner abaixo e dá uma conferida no meu livro sobre programação web com Node.js!