TDD: Como criar mocks em Node.js com Jest

Node.js

TDD: Como criar mocks em Node.js com Jest

Luiz Duarte
Escrito por Luiz Duarte em 12/01/2021
Junte-se a mais de 34 mil devs

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

Recentemente escrevi dois tutoriais sobre testes aqui no blog, um focando nos princípios do TDD e em unit tests e outro falando de integration tests. Ambas técnicas podem ser profundamente otimizadas com um terceiro conceito, de mocking, assunto do tutorial de hoje.

Mocking ou “imitação”, em uma tradução literal, é a técnica de criar imitações de objetos reais da sua aplicação que se comportam como se fosse o objeto real, exceto pelo fato de que você tem total controle sobre ele e pode direcionar o resultado/resposta ao seu bel prazer. Assim, um mock de uma classe de banco de dados, poderia retornar sempre as mesmas linhas e um mock de uma classe de API poderia simular que ela estivesse passando por um problema de performance.

Qual a utilidade disso? Testes é claro!

Um dos grandes desafios de TDD é que muitas vezes nossos testes dependem de tantos fatores externos que não podemos controlar que fica muito difícil de testá-los, especialmente se estiver buscando uma abordagem baseada em Unit Tests. Por exemplo, se seus testes dependem de banco de dados, é comum que dependam da existência de dados pré-carregados, mas ao invés disso, você pode usar mocks para retornar os dados que você precisa no seu teste, fingindo que eles vieram do banco mesmo.

Outro exemplo, eu trabalho muito com AWS e muitas das minhas aplicações interagem com serviços deles. É sabido que a AWS cobra por consumo, logo, eu teria custos a cada teste realizado, se o teste de fato utilizar o serviço real. Agora com mocks, eu posso fazer com que meu módulo cliente da AWS finja que foi até a AWS, me retornando uma resposta idêntica à que eles me fariam.

Estes são apenas alguns exemplos, mas a ideia central é essa, de criar objetos fake para que eles se passem pelos originais facilitando a sua tarefa de testar a aplicação.

Mas chega de enrolação, como fazer isso na prática?

Mocks com Jest

Tenho utilizado bastante o Jest ultimamente como suíte de testes para Node.js e ele tem algumas funcionalidades para mocking bem simples de usar, baseadas em convenções. No seu projeto, crie um arquivo jest.config.js na raiz, para configurarmos esta ferramenta, como abaixo.

Instale o Jest como uma dependência de desenvolvimento.

Neste primeiro exemplo, vamos mockar o módulo fs do Node.js, responsável por manipular o sistema de arquivos. Imagine que você quer testar uma função que usa o fs.readdirSync para, dado uma lista de arquivos, fazer alguma operação ou retornar algum valor. Esta é a função que queremos testar, dentro de mylibs/coollib.js:

Note que nosso findTestTxt depende que exista o referido arquivo para nos retornar ok, mas essa dependência de um elemento específico na infraestrutura da aplicação é algo que pode atrapalhar os testes e portanto vamos mockar.

Mas antes disso, vamos criar uma pasta __tests__ na raiz do projeto, para guardar nossos testes dentro. Nela, crie um arquivo coollib.test.js para escrevermos nosso unit test internamente.

E vamos alterar nosso package.json para incluir o script de test que chamará o Jest.

Agora quando rodarmos npm test no terminal, estando na raiz do projeto, teremos como resultado:

Falhou porque eu não tenho um arquivo test.txt junto dos testes. Não se apegue à facilidade que poderíamos resolver isso, apenas criando o referido arquivo, mas imagine que essa dependência poderia ser de alguma configuração do servidor ou qualquer recurso externo da aplicação.

Para mockarmos um módulo que esteja na pasta node_modules do Node.js, primeiro precisamos criar uma pasta __mocks__ no mesmo nível da pasta node_modules (geralmente na raiz do projeto). Este nome não é opcional, é uma convenção do Jest. Nesta pasta __mocks__ crie um módulo JS com o mesmo nome do módulo nativo que deseja mockar, neste caso, fs.js e dentro vamos criar a função mockada, como abaixo:

Aqui, estamos simulando o objeto retornado pelo módulo fs, com apenas uma função interna que é a que vamos mockar, a readdirSync, que neste mock sempre retornará o mesmo array de arquivos quando utilizada.

Para que este mock seja usado ao invés do fs original, no nosso arquivo de teste, precisamos dizer ao jest que vamos mockar o referido módulo, usando a cláusula jest.mock e passando o módulo a ser mockado (do mesmo jeito que faríamos em um require).

Agora, quando testarmos da mesma forma que antes, o resultado será ok, pois o nosso fs mock irá dizer que sempre tem alguns arquivos que pré-definimos em nossa função mockada.

Legal, hein?

Curso FullStack

Mais mocks com Jest

Mas e se quisermos mockar um módulo que não está na node_modules, um módulo próprio, por exemplo, como faríamos?

No mesmo nível do seu módulo, você deve criar a pasta __mocks__, incluindo dentro um arquivo com o mesmo nome do módulo em questão. No nosso caso, se quisermos mockar o mylibs/coollib.js, devemos criar a estrutura mylibs/__mocks__/coollib.js

O conteúdo do coollib.js mockado poderia ser algo como:

Se quisermos que ele sempre retorne true, por exemplo.

Agora, no seu arquivo de teste, fazemos da mesma forma que antes, mas atente ao fato de que devemos usar a o caminho relativo até o módulo original.

Não importa se o módulo a ser mockado foi ou não chamado ali em cima ou se ele é uma dependência interna de outro módulo, basta usar o jest.mock passando o caminho do módulo e o Jest substituirá a referência de memória do módulo pelo seu mock.

Alguns módulos populares possuem libs de mocking mais “prontas”, que dá menos trabalho de criar os seus testes. Um exemplo é a AWS-SDK-Mock, que ensino a usar neste tutorial e outro é o Sequelize-Mock que ensino a usar neste tutorial.

E quando o assunto é mockar URLs de APIs, o Nock é o melhor caminho!

E por hoje é isso, espero que tenha gostado!

Quer aprender mais comigo? Dê uma olhada em meus livros e meus cursos!

Curso Node.js e MongoDB

TAGS: nodejs

Olá, tudo bem?

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

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *