TDD: Como criar mocks em Node.js com Jest e AWS

Node.js

TDD: Como criar mocks em Node.js com Jest e AWS

Luiz Duarte
Escrito por Luiz Duarte em 15/01/2021
Junte-se a mais de 22 mil profissionais de TI

Entre para minha lista e receba conteúdos exclusivos e com prioridade

Recentemente escrevi um artigo ensinando sobre como usar a técnica de mocking, muito popular e útil para TDD, com a suíte de testes Jest, para aplicações Node.js. No entanto, senti que alguns conceitos importantes não foram abordados e que podem levar a um uso equivocado da técnica, impactando principalmente na cobertura de código da sua aplicação (code coverage).

Se você usa Jest há algum tempo, deve saber que um dos grandes benefícios dele em relação a outras suítes como Tape é a coleta de coverage metrics. Ele não apenas roda suas baterias de testes automatizados como faz a análise e apresenta métricas da proporção de testes varrendo todo o seu código, como na imagem acima.

No entanto, quando começamos a mockar nossas funções, a tendência é que os testes comecem a percorrer as funções fake e não as reais, portanto caindo drasticamente a nossa cobertura de código pois ele de fato não está sendo testado, mas sim mockado. Por exemplo, o relatório abaixo é de um projeto real, que estava com +80% de cobertura de código com testes mas que caiu drasticamente por causa de apenas dois pequenos módulos que foram mockados.

Mas o que fazer nestes casos então, devemos usar mocks e abandonar coverage? Ou devemos focar em coverage e abandonar mocks?

Nenhum dos dois, devemos primeiro entender o que devemos e mockar e o que não devemos mockar!

O que devemos mockar?

O intuito e benefícios de utilizar mocking foi dito no último artigo, mas talvez não tenha ficado claro, então vou explicar de forma diferente.

Partes da sua aplicação como um todo não são de sua gestão ou controle. Por exemplo, sua aplicação usa o pacote fs? Se o fs se comporta estranho, você mexe no fonte dele, escreve testes e commita no repo? Até poderia, já que o Node.js é open-source, mas esse é um exemplo de aspecto da sua aplicação que você não gerencia, mas depende para o seu código funcionar, toda vez que quer mexer no sistema de arquivos.

O mesmo vale para o seu banco de dados e APIs externas. Sua aplicação depende deles para funcionar, mas você não gerencia o código deles, apenas usa, geralmente através de pacotes que, apesar de serem open-source muitas vezes, você não gerencia com frequência também.

Esses pacotes e recursos não-gerenciáveis por você, cujo código você usa mas não mexe, são os fortes candidatos a serem mockados. Porque eles costumam muitas vezes tornar os seus testes dependentes, acoplados, lentos e falhos, além de exigir muito setup e cleanup, antes e depois dos testes respectivamente.

Tentando trazer de uma maneira bem prática, se você se focar em mockar módulos da node_modules ao invés de seus módulos, você estará atacando o mal das dependências na raiz e ao mesmo tempo poderá seguir buscando uma alta cobertura de testes no SEU código, pois o código da node_modules não entra no coverage report.

Para ajudar a tornar esta explicação mais convincente, vou trazer um case que acho que pode ajudar: mocks da AWS.

Curso Node.js e MongoDB

AWS SDK Mock

Quem acompanha o meu blog sabe que utilizo bastante a AWS. Serviços deles como servidores, filas, emails e bancos de dados fazem parte da maioria dos projetos que entreguei e entrego desde 2013, quando comecei a aprender a usar esta nuvem pública.

Além dos serviços de infraestrutura que você configura via console web, a AWS fornece APIs Restful e bibliotecas para as principais tecnologias do mercado, como Node.js, onde temos o fantástico aws-sdk. Ainda que eu goste da Amazon, é comum que eu crie módulos de abstração de serviços da AWS para diminuir o acoplamento da minha aplicação à eles e facilitar a manutenção.

Assim, se eu vou usar o AWS SQS para filas de mensagens (o concorrente do RabbitMQ), eu não fico chamando ele diretamente na minha aplicação, mas crio um módulo intermediário, um queueService por exemplo que por sua vez usa o SQS.

O código abaixo é de um queueService de exemplo, ele se encontra dentro de um projeto com apenas um index.js e as dependências aws-sdk, dotenv-safe e jest.

Para que ele funcione, você tem de criar uma fila na AWS, pegar credenciais de acesso válidas e configurar tudo isso e mais algumas coisas em um .env na raiz da sua aplicação (olhe o .env.example dos fontes do tutorial se quiser fazer isso).

Uma chamada como abaixo faz ele funcionar, enviando uma mensagem para a AWS.

Enquanto que o código abaixo poderia ser o index.test.js dentro de uma pasta __tests__

O que acontece se eu rodar este teste?

Se eu tiver criado a fila corretamente na Amazon, se eu tiver configurado o .env corretamente e se a minha Internet ajudar, ele deve passar com sucesso. No entanto, se qualquer uma destas variáveis der uma leve oscilada, ele não vai funcionar.

Certo, isso é a parte mais simples e óbvia. Além disso, a AWS cobra por uso, então toda vez que eu rodar este teste, estará sendo computado alguns milésimos de dólar na minha fatura no final do mês, o que não é legal.

Temos que mockar este recurso de fila, certo?

Como mockar a AWS?

Se você pegar o que ensinei no último tutorial e não pensar muito a respeito, a primeira coisa que você tende a fazer é criar uma pasta __mocks__ dentro de libs e colocar um queueService.js dentro dela, mockando a função sendMessage, certo?

No entanto, se você fizer isso, você não estará testando a AWS (ok) e nem o seu queueService (não ok), sua cobertura de teste ficará baixíssima. E isso não é legal. O SEU código DEVE estar sendo testado e com uma boa cobertura de testes ainda por cima (>80%). Sendo assim, você NÃO deve mockar ele, mas sim, mockar o código DOS OUTROS, como o código do AWS SDK, por exemplo.

Para isso, você pode usar o pacote aws-sdk-mock que facilita bastante esta atividade. Com ele, você aponta onde está o seu AWS SDK de verdade e diz quais funções você quer mockar e ele se encarrega do resto.

Por exemplo, se olharmos o conteúdo do nosso queueService, veremos que usamos apenas uma função do AWS SDK, chamada sendMessage, logo é ela que precisamos mockar e fazemos isso diretamente em nosso teste do Jest, como abaixo (não esqueça de instalar o aws-sdk-mock).

No código acima, nós importamos o módulo aws-sdk-mock e configuramos ele apontando para onde está nosso AWS SDK de verdade. Na função de teste, usamod o AWS.mock para indicar onde que o mock deve ser injetado (primeiro a classe e depois a função), passando como último parâmetro a função mock que irá substituir a original.

Assim, em qualquer ponto do código seguinte que a função SQS.sendMessage for chamada (no nosso caso ela é chamada internamente ao queueService), ela irá disparar o mock retornando o objeto que defini.

Assim, esse teste irá funciona independente das suas variáveis de ambiente para o SQS, independente das suas configurações no painel da Amazon e independente da sua conexão com a Internet. Ele é um teste unitário da função sendMessage do queueService. Ponto.

Rodando os testes agora, você manterá uma cobertura de teste alta sobre o SEU código. O código da AWS SDK, esse sim não estará sendo testado, ou sequer a infraestrutura deles, mas isso não é responsabilidade dos testes unitários, certo?

Você ainda pode querer testes integrados, mas em menor volume, bem como ainda pode querer testes manuais, em volume menor ainda, mas o seu código, esse sim você deve ter um grande volume e rodar muitas e muitas vezes a cada alteração no projeto, adição de funcionalidades, refatorações, antes de deploy, etc.

E para conseguir isso sem ter uma série de problemas, você precisa aplicar técnicas como mocking corretamente. Por isso escrevi este artigo. 🙂

Espero ter ajudado.

Um abraço e sucesso.

Quer ver na prática como utilizar escrever testes de um software real, usando a stack completa do JavaScript? Conheça meu curso clicando no banner abaixo!

Curso FullStack

Olá, tudo bem?

O que você achou deste conteúdo? Conte nos comentários.