Teste de instantâneo NodeJS: um guia essencial para melhorar a consistência da interface do usuário e a estabilidade do código

Teste de instantâneo NodeJS: um guia essencial para melhorar a consistência da interface do usuário e a estabilidade do código

Aprenda como usar o NodeJS Snapshot Testing para testar seu código de forma rápida e fácil. Aproveite ao máximo seu processo de desenvolvimento com esta ferramenta poderosa!

Imagem em destaque

Se você está lendo isso, é provável que saiba algo sobre desenvolvimento web. E se você são no mundo do desenvolvimento web, você provavelmente sabe o que é NodeJS. Mas caso não: NodeJS é um ambiente de execução JavaScript que nos permite executar código JavaScript fora do navegador. É uma ótima ferramenta para criar aplicativos da Web complexos devido à sua natureza assíncrona e sem bloqueio. Também é ideal para construir APIs.

O teste é uma parte importante do processo de desenvolvimento. Aqui, veremos um tipo importante: teste de instantâneo.

Teste em Desenvolvimento de Software

Para qualquer pessoa envolvida em serviços de desenvolvimento Node JS, escrever testes no desenvolvimento de software ou seguir práticas de TDD (Test Driven Development) é fundamental. Ninguém quer enviar código defeituoso, interface de usuário quebrada ou produtos com bugs. A melhor maneira de evitar esses problemas é testando.

Aqui, construiremos um aplicativo de tarefas simples e o testaremos com testes de snapshot.

Configurando o banco de dados

Antes de começar, como vamos escrever uma API real para este tutorial, vamos configurar nosso banco de dados. Para o tutorial, usaremos o MongoDB Atlas. Então, vamos para cloud.mongodb.com e obter nossa string de conexão. Precisaremos disso mais tarde. Nossa string de conexão será semelhante a esta => mongodb+srv://:@cluster0.45hj6.mongodb.net/?retryWrites=true&w=majority.

Observe que você precisará alterar o nome de usuário e a senha para manter a conexão.

Variáveis ​​ambientais

Enquanto mantemos nossa conexão, mantemos nosso nome de usuário e senha ocultos. Criaremos um novo arquivo chamado .env e adicionaremos nossa string de conexão a uma variável chamada MONGO_URI. Seria algo como => MONGO_URI=mongodb+srv://:@cluster0.45hj6.mongodb.net?retryWrites=true&w=majority.

Estamos usando variáveis ​​de ambiente em nosso aplicativo. Neste momento, não podemos fazer nada com isso. Mas em breve instalaremos o pacote dotenv npm e leremos esta variável em nossa aplicação.

Configuração do pacote.json

Para nossa conveniência, adicionaremos scripts ao arquivo package.json em nosso IDE Node JS. Então, vamos abri-lo e adicionar o seguinte:

  "scripts": {

    "test": "jest --detectOpenHandles --forceExit",

    "snap": "jest --updateSnapshot --detectOpenHandles --forceExit",

    "start": "node index.js",

    "dev": "nodemon index.js"

  },

Os comandos start e dev servem para iniciar nosso aplicativo. Instalaremos o pacote Nodemon para o comando dev. Instalaremos o Jest também. Aqui, a diferença entre os comandos test e snap é que o comando snap atualiza o instantâneo. Veremos o que isso significa enquanto escrevemos nossos testes de snapshot. Também estamos usando o sinalizador –forceExit para sair do conjunto de testes após a conclusão dos testes.

Construindo o aplicativo Todo

Para entender o teste de snapshot, primeiro precisamos construir nosso aplicativo. Seria ótimo se tivéssemos um aplicativo CRUD e, nesse caso, vamos construir um aplicativo de tarefas que terá as propriedades de obter, adicionar, atualizar e excluir um item de tarefas.

Começaremos criando uma nova pasta chamada “todo-app”, antes de entrar nessa pasta e executar npm init -y. Isso cria um novo arquivo package.json. Embora estejamos construindo um aplicativo de tarefas simples, devemos sempre seguir as práticas recomendadas e dividir nosso aplicativo em partes diferentes. Portanto, criaremos pastas para dividir nosso código executando testes de rotas mkdir controllers db mdels se estivermos no terminal Linux.

Agora, precisamos instalar as dependências que usaremos. Vamos executar o seguinte comando na raiz do nosso aplicativo npm i express mongoose jest mongodb nodemon supertest dotenv –save. Isso instalará as dependências necessárias para construir nosso aplicativo. Usaremos Express para iniciar nosso servidor, MongoDB para o banco de dados, Mongoose para interagir com MongoDB e Nodemon para monitorar nosso servidor sem reiniciar. dotenv nos ajudará a ocultar dados confidenciais, e Jest e SuperTest nos ajudarão a testar nosso aplicativo.

Agora que instalamos as dependências necessárias, vamos criar um novo arquivo index.js executando touch index.js no terminal e começar a codificar.

Configurando o servidor

Observe o seguinte trecho de código do arquivo index.js:

//importing dependencies

const express = require("express");

const connectDB = require("./db/connect");

require("dotenv").config ;




//importing routes

const todoRoutes = require("./routes/todoRoutes");




//creating an express app

const app = express ;




/* A middleware that parses the body of the request and makes it available in the req.body object. */

app.use(express.json );




/* This is the root route. It is used to check if the server is running. */

app.get(" (req, res) => {

  res.status(200).json({ alive: "True" });

});




/* This is the route that handles all the todo routes. */

app.use("/todos", todoRoutes);




const port = process.env.PORT    3000;




const start = async   => {

  try {

    await connectDB(process.env.MONGO_URI);

    app.listen(port, console.log(`Server is listening on port ${port}...`));

  } catch (error) {

    console.log(error);

  }

};




start ;




module.exports = app;

Embora haja comentários sobre o código, vamos repassar o que está acontecendo.

Primeiro, ao importar as dependências, você perceberá que estamos importando o connectDB de ./db/connect. Isso porque vamos nos conectar ao nosso banco de dados em um arquivo diferente. Criaremos esse arquivo em breve.

Segundo, estamos importando todoRoutes de ./routes/todoRoutes. Isso também ocorre porque vamos escrever nossas rotas lá.

Após utilizar as rotas via app.use(“/todos”, todoRoutes);, estamos configurando a porta e iniciando o servidor. Também estamos exportando o aplicativo para que possamos usá-lo em nossos testes.

Conectando-se ao banco de dados

Como queremos separar nossas preocupações, dentro da pasta db criaremos um arquivo chamado connect.js e escreveremos o seguinte código:

const mongoose = require("mongoose");

mongoose.set("strictQuery", false);




const connectDB = (url) => {

  return mongoose.connect(url, {});

};




module.exports = connectDB;

Até obtermos o mangusto e podemos nos conectar ao banco de dados. No arquivo index.js, a última função foi denominada start:

const start = async   => {

  try {

    await connectDB(process.env.MONGO_URI);

    app.listen(port, console.log(`Server is listening on port ${port}...`));

  } catch (error) {

    console.log(error);

  }

};

Como você pode ver, o connectDB é importado aqui com o MONGO_URI que salvamos anteriormente no arquivo .env. Agora que nosso servidor pode se conectar ao banco de dados, é hora de criar um modelo.

Criando o modelo

Entraremos no diretório de rotas e construiremos um novo arquivo chamado todoModel.js. Preenchemos esse arquivo com o seguinte código:

const mongoose = require("mongoose");




const todoSchema = mongoose.Schema({

  name: { type: String, required: true },

});




module.exports = mongoose.model("Todo", todoSchema);

Nossos todos terão apenas um nome. O ID será gerado automaticamente pelo MongoDB. Aqui estamos exportando o esquema com o nome “Todo”. Usaremos esse nome quando quisermos interagir com o banco de dados.

Criando os controladores

Agora que temos um modelo, podemos criar os controladores. Iremos para a pasta controllers e iniciaremos um arquivo chamado todoControllers.js. Os controladores exigirão que o modelo funcione e, como o modelo exigia o Mongoose, podemos usar comandos do Mongoose nos controladores. Vamos começar obtendo todos os todos. Agora, nada existe no banco de dados. Estamos apenas escrevendo a lógica que receberá todos assim que forem preenchidos.

const Todo = require("../models/todomodel");




const getAllTodos = async (req, res) => {

  try {

    const todos = await Todo.find({}).exec ;

    res.status(200).json({ todos });

  } catch (error) {

    res.status(500).json({ msg: error });

  }

};

Primeiro importamos o modelo e depois, com a função assíncrona getAllTodos, queremos obter todos os todos. Para nossas funções restantes, usaremos a mesma sintaxe async/await try/catch, pois ela simplifica a legibilidade do código e facilita a depuração.

Sob o código acima, adicionamos as seguintes linhas:

const createTodo = async (req, res) => {

  try {

    const todo = await Todo.create(req.body);

    res.status(201).json({ todo });

  } catch (error) {

    res.status(500).json({ msg: error });

  }

};




const updateTodo = async (req, res) => {

  try {

    const todo = await Todo.findOneAndUpdate({ _id: req.params.id }, req.body, {

   new: true,

    }).exec ;

    res.status(200).json({ todo });

  } catch (error) {

    res.status(500).json({ msg: error });

  }

};




const deleteTodo = async (req, res) => {

  try {

    const todo = await Todo.findOneAndDelete({ _id: req.params.id }).exec ;

    res.status(200).json({ todo });

  } catch (error) {

    res.status(500).json({ msg: error });

  }

};




module.exports = {

  getAllTodos,

  createTodo,

  updateTodo,

  deleteTodo,

};

Criar, atualizar e excluir todo segue o mesmo padrão de criação de um, mas precisamos fazer outra coisa: exportar essas funções. Vamos usá-los em nossas rotas.

Criando as Rotas

Para criar rotas, iremos até a pasta de rotas, criaremos um arquivo chamado todoRoutes.js e inseriremos o seguinte código:

const express = require("express");

const router = express.Router ;




const {

  getAllTodos,

  createTodo,

  updateTodo,

  deleteTodo,

} = require("../controllers/todoControllers");




router.route("




router.route("/:id").patch(updateTodo).delete(deleteTodo);




module.exports = router;

Agora, estamos exigindo expresso e roteador do expresso. A seguir, importaremos as funções que criamos e exportamos nos controladores. Então, estamos usando o roteador para especificar qual rota chamará qual função.

No nosso caso, a rota base chamará a função getAllTodos no caso de uma solicitação get e criará Todo no caso de uma solicitação post. Para corrigir e excluir, precisamos especificar o ID da tarefa específica que queremos atualizar ou excluir. É por isso que estamos usando a sintaxe /:id. Agora podemos exportar o roteador e usá-lo em nosso arquivo index.js.

Agora, as linhas const todoRoutes = require(“./routes/todoRoutes”); e app.use(“/todos”, todoRoutes); no arquivo index.js faz sentido.

Se rodarmos o servidor e testarmos via Postman ou Insomnia, poderemos fazer operações CRUD. Mas queremos fazer mais: queremos testar nosso código usando instantâneos para que possamos ver se nosso código está funcionando conforme o esperado.

Testando o Código

Depois de elaborar meticulosamente nosso aplicativo, a próxima etapa crucial é garantir sua confiabilidade e eficácia por meio de testes rigorosos.

Teste de instantâneo

O teste de instantâneo é diferente do teste padrão. Além disso, é importante observar que, embora geralmente seja usado com tecnologias de front-end como ReactJs, também pode ser útil no desenvolvimento de back-end. O que é isso exatamente? Para entender o teste de instantâneo, é útil primeiro entender o teste em si.

No desenvolvimento de software, existem diferentes métodos de teste, desde testes unitários até testes ponta a ponta. Existem também ferramentas para escrever testes usando métodos como Jest, Mocha, Chai, Cypress e muito mais.

Aqui, usaremos Jest. Normalmente, quando escrevemos testes com Jest, há certas coisas que procuramos. Queremos escrever um teste que verifique se o código funciona conforme o esperado.

Pense no seguinte exemplo: como estamos construindo uma aplicação CRUD, podemos querer verificar se o método patch funciona conforme o esperado. Digamos que haja uma tarefa “Comprar velas” e, através de uma solicitação de patch, queremos alterá-la para “Comprar isqueiro”. No conjunto de testes, escreveríamos um teste que verificaria se a tarefa foi alterada conforme pretendido ou não. Nós “esperaríamos” que a tarefa em questão fosse “Compre mais leve”. Se for, o teste será aprovado. Caso contrário, o teste falhará.

Agora, o teste de instantâneo é diferente. Em vez de esperar um determinado comportamento e iniciar uma situação de aprovação/reprovação de acordo com isso, tiramos instantâneos do nosso código em seu estado em um determinado momento e comparamos com o instantâneo anterior. Se houver uma diferença, o teste falha. Se não houver diferença, o teste passa.

Isso ajuda a reduzir a possibilidade de alterações indesejadas. Se houver uma mudança, a depuração agora seria muito mais fácil.

Agora, vamos codificar com um caso de teste de snapshot típico. Já instalamos o Jest e o SuperTest, outra ferramenta que nos ajudará a testar solicitações de API.

Escrevendo o teste de instantâneo

Primeiro, vamos para a pasta de testes que criamos antes e adicionamos o arquivo index.test.js. Jest encontrará este arquivo automaticamente. Agora, dentro do arquivo, começaremos escrevendo as seguintes linhas de código:

const mongoose = require("mongoose");

const request = require("supertest");

const app = require("../index");

const connectDB = require("../db/connect");

require("dotenv").config ;




/* Connecting to the database before each test. */

beforeEach(async   => {

  await connectDB(process.env.MONGO_URI);

});




/* Dropping the database and closing connection after each test. */

afterEach(async   => {

  // await mongoose.connection.dropDatabase ;

  await mongoose.connection.close ;

});

Começamos importando as dependências necessárias. Posteriormente, definimos dois métodos: beforeEach e afterEach. Eles serão executados antes e depois de cada teste. No método beforeEach, estamos nos conectando ao banco de dados. No método afterEach, eliminamos o banco de dados e fechamos a conexão. Agora, escreveremos nosso primeiro teste nessas linhas:

describe("GET /todos",   => {

  it("should return all todos", async   => {

    const res = await request(app).get("/todos");

    // expect(res.statusCode).toEqual(200);

    // expect(res.body).toHaveProperty("todos");

    expect(res.body).toMatchSnapshot ;

  });

});

Agora, executaremos npm run test no terminal. Isso corresponderá a jest –detectOpenHandles –forceExit conforme definimos nos scripts package.json. Observe que as linhas confirmadas são como normalmente testaríamos a resposta da API. Mas como estamos fazendo testes de instantâneos, usamos uma abordagem diferente com a palavra-chave toMatchSnapshot.

Depois de executar o comando npm run test, se você olhar a pasta de testes, perceberá que há outra pasta chamada __snapshots__ dentro dela. Essa pasta possui um arquivo chamado index.test.js.snap. Se você abrir esse arquivo, verá que ele contém:

// Jest Snapshot v1, 




exports(`GET /todos should return all todos 1`) = `

{

  "todos":  ,

}

`;

Isso significa que tiramos com sucesso um instantâneo do estado atual do aplicativo. Como ainda não postamos todos, o array todos retorna vazio. Agora, sempre que fizermos uma alteração e executarmos os testes, ele irá comparar o estado atual da aplicação com este snapshot. Se houver uma diferença, o teste falha. Se não houver diferença, o teste passa. Vamos tentar. Em index.test.js, adicionaremos o seguinte teste:

describe("POST /todos",   => {

  it("should create a new todo", async   => {

    const res = await request(app).post("/todos").send({

   name: "Buy candles",

    });




    expect(res.body).toMatchSnapshot ;

  });

});

Este teste criará uma nova tarefa chamada “Comprar velas”. Agora, tire um instantâneo do estado atual do aplicativo. Vamos executar o teste npm run novamente e ver o que acontece. Os testes passam. Mas se você olhar o arquivo index.test.js.snap, verá que ele mudou:

// Jest Snapshot v1, 




exports(`GET /todos should return all todos 1`) = `

{

  "todos":  ,

}

`;




exports(`POST /todos should create a new todo 1`) = `

{

  "todo": {

    "__v": 0,

    "_id": "646dba457c9da2bc152c498a",

    "name": "Buy candles",

  },

}

`;

Vamos refazer os testes e ver o que acontece. Agora, os testes falham. Se você verificar o atlas do MongoDB e olhar sua coleção, verá que há dois “Comprar velas” todos, com IDs diferentes. Mas no arquivo de instantâneo, tínhamos apenas um. É por isso que falha. Ele compara o estado do aplicativo em que o instantâneo foi obtido com o atual e mostra as alterações. Se você olhar em seu terminal, verá os detalhes do teste.

Podemos atualizar nosso instantâneo. Vamos mudar “Comprar velas” para “Comprar isqueiro” por conveniência e executar npm run snap desta vez. Você deve se lembrar que este comando corresponde a jest –updateSnapshot –detectOpenHandles –forceExit em scripts package.json. Os testes passam. Ele também atualizará o arquivo de instantâneo. Se voltarmos ao arquivo index.test.js.snap e vermos o que há dentro, devemos ver isto:

// Jest Snapshot v1, 




exports(`GET /todos should return all todos 1`) = `

{

  "todos": (

    {

   "__v": 0,

   "_id": "646dba457c9da2bc152c498a",

   "name": "Buy candles",

    },

    {

   "__v": 0,

   "_id": "646dba747fda9c2f1fb94a7d",

   "name": "Buy candles",

    },

  ),

}

`;




exports(`POST /todos should create a new todo 1`) = `

{

  "todo": {

    "__v": 0,

    "_id": "646dbcb461df5575a4a63bf1",

    "name": "Buy lighter",

  },

}

`;

Vejamos outro exemplo. O teste de instantâneo é especialmente útil em casos em que pode haver alterações inesperadas. Por exemplo, há uma chance de que um dos todos seja excluído. Vamos comentar a solicitação de postagem por conveniência e adicionar outro teste ao nosso arquivo index.test.js:

describe("DELETE /todos/:id",   => {

  it("should delete a todo", async   => {

    const res = await request(app).delete("/todos/646ce381a11397af903abec9");




    expect(res.body).toMatchSnapshot ;

  });

});

Observe o ID em delete(“/todos/646ce381a11397af903abec9”); é o ID da primeira tarefa da nossa coleção. Nós fornecemos por código rígido. Agora, se executarmos o npm run test, ele deverá falhar e nos mostrar as diferenças. No terminal, os testes devem passar, e quando olharmos nosso arquivo de snapshot, devemos ver isto:

// Jest Snapshot v1, 




exports(`DELETE /todos/:id should delete a todo 1`) = `

{

  "todo": {

    "__v": 0,

    "_id": "646dba457c9da2bc152c498a",

    "name": "Buy candles",

  },

}

`;

exports(`GET /todos should return all todos 1`) = `

{

  "todos": (

    {

   "__v": 0,

   "_id": "646dba457c9da2bc152c498a",

   "name": "Buy candles",

    },

    ...

    ...

O ID que excluímos ainda está no instantâneo. Precisamos atualizá-lo. Se executarmos npm run snap e olharmos o arquivo de instantâneo, veremos que a tarefa com o ID especificado desapareceu. A partir daqui, podemos continuar brincando com nosso aplicativo e ver se ele mudou.

Benefícios do teste instantâneo

Existem vários benefícios menos óbvios na realização de testes de snapshot em aplicativos nodeJS. Alguns deles são:

  • Teste de regressão: O teste de instantâneo é ótimo para garantir que qualquer alteração feita não prejudique seu aplicativo. Você pode executar os testes e ver se há alguma alteração inesperada no aplicativo.
  • Verificação de contrato de API: O teste de instantâneo é útil para confirmar se o contrato entre o front-end e o back-end não está quebrado. Ao tirar instantâneos das respostas da API, você pode garantir que o front-end está obtendo os dados esperados.
  • Documentação: Ao tirar snapshots, você pode comunicar o estado do seu aplicativo e que tipo de dados devem retornar em qual cenário para seus colegas de equipe.
  • Desenvolvimento Colaborativo: O teste de instantâneo pode ser benéfico na comunicação entre desenvolvedores front-end e back-end. Com snapshots, os desenvolvedores de front-end podem antecipar e lidar com quaisquer alterações no back-end.
  • Refatoração e alterações de código: Na refatoração de código, os snapshots fornecem uma rede de segurança. Você pode garantir que suas alterações não alterem nada indesejado.

Conclusão

Aqui, aprendemos como realizar testes de snapshot em aplicativos nodeJS, instalar e configurar o Jest, escrever testes e tirar snapshots do estado atual do aplicativo. Também revisamos os benefícios do teste instantâneo. Agora, você deve ter uma ideia mais clara do que envolve um teste de instantâneo e por que ele auxilia no processo de desenvolvimento.

Se você gostou deste artigo, você pode gostar;

  • Desbloqueie o poder dos microsserviços Node.JS
  • Alterar versão do nó: um guia passo a passo
  • Cache Node JS: aumentando o desempenho e a eficiência
  • Desbloqueando o poder do Websocket Nodejs

Perguntas frequentes

Quais bibliotecas são comumente usadas para testes de instantâneos em Node.js?

Jest é uma das bibliotecas de teste mais populares para testes de snapshots em Node.js. Ele permite criar e gerenciar facilmente instantâneos de seus componentes, simplificando a identificação de quaisquer alterações inesperadas. Outra biblioteca que pode ser usada para testes de snapshots é a Ava, embora não seja tão amplamente usada quanto a Jest.

Quando devo usar o Snapshot Testing em meu projeto Node.js?

O teste de instantâneo é melhor usado quando você deseja garantir que as alterações em seu código não alterem inesperadamente os componentes da UI. É especialmente útil para aplicativos grandes e complexos onde pode ser difícil verificar manualmente a interface do usuário após cada alteração. No entanto, o teste de instantâneo não deve ser a única estratégia de teste, pois não garante a correção da lógica de negócios, apenas a consistência da IU.

Como um arquivo de instantâneo de referência é criado?

Um arquivo de instantâneo de referência normalmente é criado na primeira vez que você executa um teste de instantâneo. A estrutura de teste (como Jest) criará automaticamente um instantâneo do estado atual do componente de UI ou outra saída que está sendo testada e o armazenará em um arquivo. Este arquivo de teste será então usado como instantâneo de referência para testes subsequentes.

O que devo fazer se meu teste de snapshot falhar devido a uma incompatibilidade de snapshot?

Se um teste falhar devido a uma incompatibilidade de arquivos de instantâneo, isso significa que o estado atual da UI ou outra saída que está sendo testada não corresponde aos valores de instantâneo armazenados. Você deve primeiro investigar para determinar se a alteração foi intencional ou resultado de um bug. Se a alteração foi intencional e o novo estado estiver correto, você poderá atualizar o instantâneo conforme descrito acima. Se a alteração não foi intencional, será necessário depurar a causa da alteração inesperada.

Conteúdo Relacionado

O Rails 8 sempre foi um divisor de águas...
A GenAI está transformando a força de trabalho com...
Entenda o papel fundamental dos testes unitários na validação...
Aprenda como os testes de carga garantem que seu...
Aprofunde-se nas funções complementares dos testes positivos e negativos...
Vídeos deep fake ao vivo cada vez mais sofisticados...
Entenda a metodologia por trás dos testes de estresse...
Descubra a imprevisibilidade dos testes ad hoc e seu...
A nomeação de Nacho De Marco para o Fast...
Aprenda como os processos baseados em IA aprimoram o...
O Node.js, o popular tempo de execução JavaScript assíncrono...
A web está em constante evolução, e com ela,...
A Inteligência Artificial (IA) tem sido um tema cada...
Você já se sentiu frustrado com a complexidade de...
O OpenStack é uma plataforma de computação em nuvem...
Você já se sentiu frustrado com a criação de...
A era digital trouxe uma transformação profunda na forma...
Nos dias atuais, a presença digital é fundamental para...
Torna al blog

Lascia un commento

Si prega di notare che, prima di essere pubblicati, i commenti devono essere approvati.