Boas-vindas ao curso de testes assíncronos com Node.js. Meu nome é Tiago Bussola, sou instrutor na Escola de Programação e DevOps.
Audiodescrição: Tiago é um homem branco, com cabelo e barba escuros. Ele usa óculos de grau e veste uma camiseta branca. Ao fundo, há uma estante preenchida com livros e um armário branco iluminado por luzes azuis.
Este conteúdo é destinado a quem tem interesse em aprender a testar scripts assíncronos em Node.js e deseja aprofundar seus conhecimentos em testes de API REST.
Neste curso, aprenderemos a utilizar o Node Test Runner para testar scripts assíncronos, além de como usar o Mocha, o Chai e o Sinon para aprimorar nossos testes. Exploraremos a configuração do Jest para projetos com módulos ECMAScript, o teste de APIs com rotas autenticadas e o uso do Copilot para agilizar a escrita dos testes. Também abordaremos a identificação de testes falsos e a configuração de variáveis de ambiente para testes utilizando o Jest Test Runner.
Tudo isso será aplicado em um projeto de biblioteca que permite gerenciar livros cadastrados e autenticar usuários através de nossa API REST. Veremos também a criação e importação de livros no banco de dados por meio de scripts utilizando Node.js Streams.
Para aproveitar melhor este curso, é recomendável que já tenhamos conhecimento prévio sobre como criar uma API REST utilizando Node.js e Express, manipular arquivos com os módulos internos ReadFile ou Node.js Streams, e que tenhamos concluído o curso anterior de testes unitários e de integração com Node.js, além de possuir noções básicas de teste.
Aproveitemos os recursos da plataforma. Além dos vídeos e das atividades, temos também o fórum e a comunidade do Discord para nos dar apoio. Vamos estudar?
Dando prosseguimento ao curso de testes que realizamos anteriormente, vamos testar um novo projeto com alguns scripts e interações assíncronas desta vez. O projeto é uma nova API, então, embora tenhamos o mesmo tema, que são livros e usuários autenticados, precisamos preparar todo o projeto e o ambiente inicial.
Primeiro, vamos verificar o arquivo package.json
para garantir que todas as dependências necessárias estão listadas. Caso não estejam instaladas, devemos rodar o comando npm install
. Aqui está o conteúdo do package.json
:
{
"name": "4591-nodejs-testing",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watchAll",
"test:coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage --watchAll",
"test:ci": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage",
"lint": "eslint ./src",
"lint:fix": "eslint ./src --fix",
"dev": "nodemon ./src/server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"bcrypt": "^5.1.1",
"express": "^4.21.2",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.10.2"
},
"devDependencies": {
"@faker-js/faker": "^9.6.0",
"chai": "^5.2.0",
"jest": "^29.7.0",
"mocha": "^11.1.0",
"sinon": "^20.0.0",
"supertest": "^7.1.0"
}
}
Com as dependências verificadas, podemos instalar tudo com o seguinte comando:
npm install
No docker-compose
, deixamos um banco de dados MongoDB preparado apenas para realizarmos os testes. Portanto, utilizamos o comando docker-compose up -d
. Aqui está a configuração do docker-compose.yml
:
version: '3'
services:
mongo:
image: mongo
volumes:
- ./data:/data/db
ports:
- '27017:27017'
Para iniciar o MongoDB, executamos:
docker compose up -d
No arquivo .env
, temos algumas variáveis que utilizaremos posteriormente. Não agora, mas temos a variável APP
para definir se nosso aplicativo é de desenvolvimento ou de produção, uma palavra secreta para o nosso JWT, e duas URLs do MongoDB: uma para teste e outra para produção.
MONGODB_URI=mongodb://0.0.0.0:27017/books
MONGODB_TEST_URI=mongodb://0.0.0.0:27017/books_test
JWT_SECRET=batataDoce
APP="dev"
Como mencionado, temos uma API muito similar à do curso anterior de testes unitários e testes de APIs. A diferença aqui é que agora estamos lidando com tabelas um pouco diferentes e utilizaremos alguns scripts para popular nosso banco de dados. Como o banco está vazio, geraremos alguns livros aleatórios e os importaremos no banco.
Para simular que estamos gerando esse arquivo para enviar a nós mesmos e inserir no banco de dados, utilizamos o script generateBooks
. Ele cria uma stream de escrita e percorre até o number of books
, onde definimos um número de livros e o arquivo que queremos escrever. Aqui está o código do script:
import fs from "fs";
import { faker } from "@faker-js/faker";
export async function generateBooksToNDJSON(filePath, numberOfBooks) {
const writeStream = fs.createWriteStream(filePath);
for (let i = 0; i < numberOfBooks; i++) {
const book = {
title: faker.lorem.words(3),
author: faker.person.fullName(),
ISBN: faker.string.numeric(10),
};
writeStream.write(JSON.stringify(book) + "\n");
}
writeStream.end();
}
generateBooksToNDJSON("books.ndjson", 100000);
Esse script monta um objeto com três propriedades: o título, que é formado por três palavras aleatórias do lorem ipsum; o autor, escolhido de uma lista extensa do faker; e o ISBN, que é uma string formada por dez números aleatórios gerados pelo faker. Em seguida, escrevemos cada um dos livros no arquivo, transformando o objeto em uma string e quebrando a linha no final. Dessa forma, conseguimos montar um arquivo no formato NDJson.
Abaixo, fizemos a chamada da função para criar um arquivo chamado books.ndjson
, gerando 100 mil livros. Vamos limpar o terminal. Neste momento, o banco de dados não precisa estar ativo, então não há problema. Tudo que fazemos é rodar o script com o comando:
node scripts/generateBooks.js
O processo demora menos de um segundo. Ao abrir o arquivo, observamos que o título é formado por três palavras, como "Jimmy Johnson", e um número de dez caracteres, todos aleatórios. Tudo está em formato de string no nosso esquema. Ao pressionar control enter, verificamos que temos 100 mil registros criados.
Agora, precisamos trabalhar em alguns testes. Todos esses livros foram gerados de forma assíncrona. No próximo vídeo, utilizaremos o Node Test Runner, o Test Runner nativo do Node, para testar se estamos gerando o número correto de livros, se os livros estão no formato esperado, entre outros aspectos. Nos vemos no próximo vídeo.
Agora que validamos que nosso script está funcionando conforme o esperado e gerou a quantidade de linhas com registros que solicitamos, vamos escrever o teste dele usando o testRunner nativo do Node.
Um script não é diferente de rotas ou funções. Na verdade, ele é uma função que executa o que colocamos ali dentro, e precisamos validar se isso realmente está de acordo com o que esperamos. Precisamos verificar se ele está gerando um livro válido, se o formato do arquivo está correto e se a quantidade de linhas é exatamente a que passamos. Assim como fazemos um teste unitário de uma função, devemos fazer o teste unitário também para nossos scripts, para garantir que eles funcionem da maneira esperada.
Vamos, então, à pasta raiz do projeto. Criaremos uma pasta chamada "testes" e, dentro dela, uma pasta chamada "scripts". Em seguida, criaremos um novo arquivo chamado testgeneratebooks.js
. Por enquanto, não utilizaremos a nomenclatura de spec nem a nomenclatura de .teste, mas deixaremos isso para depois.
Primeiro, vamos colar alguns imports que já deixaremos prontos para não perdermos muito tempo digitando. Estamos importando a função test
do Node test, do test runner nativo, o assert
, o fs
do fs/promises
, o path
e o url
para normalizar nossos caminhos. Também importaremos a função do nosso script para gerar os livros.
import { test } from "node:test";
import assert from "node:assert";
import fs from "fs/promises";
import path from "path";
import { fileURLToPath } from "url";
import { generateBooksToNDJSON } from "../../scripts/generateBooks.js";
Como estamos trabalhando dentro da pasta de teste, não queremos que, ao executar nosso script, o arquivo seja criado na raiz do projeto. O Node cria o arquivo no local de onde você executou. Portanto, se executarmos da raiz, ele criará o arquivo na raiz. Queremos que o arquivo de livros seja criado dentro da pasta "scripts", pois é lá que estamos trabalhando agora, para não poluir nosso ambiente.
Tudo relacionado ao teste deve ser mantido em um local específico. Para isso, vamos realizar uma normalização. Assim, const __dirname
recebe path.dirname
, passando o fileURLToPath
e o import.meta.url
.
const __dirname = path.dirname(fileURLToPath(import.meta.url));
Se estivermos acostumados a usar outros test runners, como no curso de testes unitários em que utilizamos o Jest, temos funções como before
, beforeAll
, afterAll
, que executam antes e depois dos testes. No ambiente nativo, ainda não dispomos dessas funções. Portanto, desejamos que, sempre que nosso script de teste for executado, ele limpe o arquivo de livros no início e no final, removendo esses arquivos para garantir que cada teste seja independente.
Como não temos as funções afterAll
e beforeAll
, vamos criar uma função para limpar o arquivo e chamá-la em cada um dos testes, conforme necessário. Vamos criar uma função assíncrona chamada cleanupFile
, que receberá como parâmetro o caminho do arquivo, filePath
.
async function cleanupFile(filePath) {
try {
await fs.unlink(filePath);
} catch (error) {
if (error.code !== 'ENOENT') throw error;
}
}
Agora, vamos criar um próximo teste, semelhante ao que vimos no Jest. Podemos definir um nome para o teste e o que ele executará. Neste caso, o teste deve gerar o número correto de livros. Passaremos uma função síncrona como callback, que será executada no teste.
test('Deve gerar o número correto de livros', async () => {
const testFilePath = path.join(__dirname, "test-books.ndjson");
const numberOfBooks = 100;
await cleanupFile(testFilePath);
await generateBooksToNDJSON(testFilePath, numberOfBooks);
const content = await fs.readFile(testFilePath, "utf-8");
const lines = content.trim().split("\n");
assert.strictEqual(lines.length, numberOfBooks, `Deveria gerar ${numberOfBooks} livros`);
// await cleanupFile(testFilePath);
});
Dentro do teste, definiremos const testFilePath
como path.join
, passando __dirname
e test-books.ndjson
, que será o nome do arquivo de livros utilizado. Assim, ele será gerado dentro da pasta "script" e se chamará test-books.ndjson
. Também definiremos const numberOfBooks
para especificar o número de livros, facilitando alterações futuras.
Iniciaremos chamando cleanupFile
para garantir que o arquivo test-books
não exista antes de criá-lo. Em seguida, chamaremos await generateBooksToNDJSON
, passando o caminho do arquivo de teste e o número de livros a serem inseridos.
Após isso, contaremos o número de linhas. Utilizaremos const content = await fs.readFile
, lendo o arquivo test-books.ndjson
em utf8
. Em seguida, criaremos const lines
, que receberá o conteúdo da leitura, removendo espaçamentos e dividindo a string por \n
. Isso nos permitirá contar quantas linhas existem no arquivo e verificar se o número de livros está correto.
Faremos uma asserção com assert.strictEqual
, passando o valor atual lines.length
, o valor esperado numberOfBooks
e uma mensagem opcional para facilitar a identificação de erros no terminal. A mensagem será "Deveria gerar" seguido do número de livros.
No final, como teremos outros testes, deletaremos o arquivo. No entanto, na primeira execução, deixaremos essa linha comentada para verificar se o arquivo foi gerado corretamente.
Para executar, abriremos o terminal, fecharemos o explorador de arquivos e executaremos node testes-scripts-testGenerateBooks.js
. Ao dar enter, o teste falhou, indicando que não encontrou o arquivo, apesar de ter gerado um arquivo com 100 registros. Isso ocorreu devido a uma race condition, que resolveremos no próximo vídeo.
O curso Node.js: testando API's REST e scripts assíncronos possui 200 minutos de vídeos, em um total de 51 atividades. Gostou? Conheça nossos outros cursos de Node.JS em Programação, ou leia nossos artigos de Programação.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
Impulsione a sua carreira com os melhores cursos e faça parte da maior comunidade tech.
1 ano de Alura
Assine o PLUS (1 ANO) e garanta:
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
No Discord, você tem acesso a eventos exclusivos, grupos de estudos e mentorias com especialistas de diferentes áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Acelere o seu aprendizado com a IA da Alura e prepare-se para o mercado internacional.
1 ano de Alura
Todos os benefícios do PLUS (1 ANO) e mais vantagens exclusivas:
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Transforme a sua jornada com benefícios exclusivos e evolua ainda mais na sua carreira.
1 ano de Alura
Todos os benefícios do PRO (1 ANO) e mais vantagens exclusivas:
Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Brinde aniversário Alura
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.
Enquanto durarem os estoques