Alura > Cursos de Programação > Cursos de Node.JS > Conteúdos de Node.JS > Primeiras aulas do curso Node.js: implementando testes em uma API Rest

Node.js: implementando testes em uma API Rest

Boas práticas com testes unitários - Apresentação

Olá, sou Raphael Girão, instrutor da Alura. Dou-lhe as boas-vindas ao nosso curso de testes com o Node.js.

Audiodescrição: Raphael Girão se identifica como uma pessoa morena de cabelos pretos e olhos castanhos. Está com uma camiseta preta, sentado em uma cadeira gamer preta. Ao fundo, estante branca com decorações e iluminação azul.

O que vamos aprender?

Neste curso, você vai aprender sobre padrões de testes e boas práticas na criação deles, assim como a criar:

Além disso, será abordado o desenvolvimento orientado a testes utilizando TDD (Test Driven Development).

Pré-requisitos

Todos esses conceitos serão aplicados em nossa API de livraria, onde podemos cadastrar livros, autores e pessoas usuárias. As pessoas usuárias podem se autenticar e, quando autenticadas, podem também alugar livros.

Se você não está acompanhando a formação de autenticação, testes e segurança em Node.js, será necessário possuir alguns pré-requisitos, como:

Além de nossos vídeos, oferecemos exercícios, um fórum de discussão e uma comunidade no Discord da Alura para solucionar suas dúvidas.

Então, vamos codar?

Boas práticas com testes unitários - Aplicando o padrão triple A

Olá, pessoal. Vamos dar início ao curso de testes com o Node.js. Para este curso, vamos utilizar uma API de uma livraria.

Dentro dessa API temos algumas funcionalidades novas. Entre elas, está a parte de usuário, onde podemos fazer o cadastramento de pessoas usuárias e elas podem se autenticar.

Quando essas pessoas usuárias estão autenticadas, elas podem alugar livros, cadastrar novos livros e pessoas autoras. Vamos dar uma olhada no projeto?

Estrutura do projeto

Ao abrir o menu lateral do VS Code (atalho "Ctrl + Shift + E"), conseguimos conferir as pastas e a estrutura do nosso projeto, ou seja, a arquitetura.

Dentro de "src" (lê-se source), temos as pastas de:

Dentro dessa pasta de testes, já temos duas pastas que vieram do curso anterior, chamadas "models" e "routes".

Portanto, vamos continuar a seguir esse padrão, para que quem venha a utilizar o projeto possa entender todos os testes criados. Esse padrão se baseia pela pasta "src".

Criação de novo teste

Como queremos criar um novo teste, vamos escolher uma dessas pastas para testar. Nesse caso, vamos escolher a pasta de "services" e conferir quais os arquivos temos dentro dela. Entre eles, temos authService.js, autoresService.js, editorasService.js. Vamos precisar escolher um deles.

Vamos escolher authService.js, que é a parte nova no nosso projeto, onde temos o módulo de cadastro e login de pessoas usuárias. Esse arquivo será escolhido porque é um módulo novo e ainda não possui testes.

Nele, temos duas funções, a de login() e cadastrarUsuario(). Para criar nossos primeiros testes, vamos escolher a parte de cadastro de pessoas usuárias. Isso nos permitirá definir um novo padrão de testes, que facilitará o entendimento de pessoas novas no projeto.

authService.js:

class AuthService {

  async login(data) {
    // código omitido…
  }

  async cadastrarUsuario (data) {
    data.senha = await bcryptjs.hash (data.senha, 8);

    const usuario = new Usuario (data);
    try {
      const resposta = await usuario.salvar (usuario);
      return { message: 'usuario criado', content: resposta };
    } catch (err) {
      throw new Error(err.message);
    }
  }
}

Agora, vamos fechar o arquivo authService.js e abrir a pasta de testes. Dentro dela, vamos criar uma nova pasta chamada "services", baseada na arquitetura do source.

Em "tests > services", vamos criar um novo arquivo que será referente ao arquivo de service para o qual vamos criar os testes. No nosso caso, é o authService. Portanto, o nome do arquivo será authService.test.js, informando que é um arquivo de teste.

Agora que criamos o arquivo, precisamos definir o que vamos testar, quais são as regras que vamos validar na função de cadastrarUsuario().

Para isso, trouxe um documento onde temos todas as regras que vamos precisar testar. Vamos abrir o bloco de notas e copiar as regras que vamos utilizar.

- O usuário deve possuir um nome, email e senha
- A senha do usuário precisa ser criptografada quando for salva no banco de dados
- Não pode ser cadastrado um usuário com e-mail duplicado
- Ao cadastrar um usuário, deve ser retornado mensagem informando que o usuário foi cadastrado
- Ao cadastrar um usuário, validar retorno do usuário

Então, vamos copiar e levar essas regras para o projeto. Vamos selecionar tudo ("Ctrl + A"), copiar ("Ctrl + C") e minimizar o bloco de notas. Agora, na pasta de "test > services" no VS Code, vamos criar um novo arquivo chamado testes-authService.txt. Nele, vamos colar ("Ctrl + V") as regras dos testes.

Já que temos nossas regras definidas, vamos selecionar e copiar a primeira regra para levá-la aos nossos testes. Em authService.test.js, vamos criar nosso primeiro cenário de teste.

Começaremos importando o describe para poder unificar todos os testes da função de cadastrarUsuario(). Na primeira linha, faremos um import e chamaremos a função describe entre chaves.

Depois de importá-la de @jest/globals, vamos unificar colocando um bloco de describe na próxima linha.

Primeiro, descrevemos o que queremos. Por exemplo, a string Testando a authService.cadastrarUsuario, que é o nome da função a ser testada.

Ele já fez a chamada da função e, dentro dela, vamos criar nosso primeiro cenário de teste. Em uma nova linha, vamos dar um it() e colar a mensagem O usuário deve possuir um nome, email e senha que é a regra do primeiro teste.

Depois disso, vamos acrescentar uma vírgula e chamar o escopo do teste. Para padronizar o teste, vamos criar uma estrutura de Mock (simulação), que será o dado que vamos passar para testar.

Dentro da arrow function, vamos criar uma constante chamada usuarioMock que será igual à abre e fecha chaves. Como é um teste em que queremos validar se a service está recebendo as informações adequadamente, é um teste de erro.

Portanto, não vamos passar todas as informações do usuário. Deixaremos faltando alguns campos. Então, vamos passar um nome como Raphael e um email que será rafael@teste.com.br. Como queremos validar um erro, não vamos passar uma senha.

authService.test.js:

import { describe } from '@jest/globals';

describe('Testando a authService.cadastrarUsuario', () => {
  it('O usuario deve possuir um nome, email e senha', () => {
    const usuarioMock = {
      nome: 'Raphael',
      email: 'raphael@teste.com.br'
    };
  });
});

Feito isso, vamos chamar a service para testar a função de cadastro. No começo do arquivo, vamos importar AuthService a partir do local correto do diretório. Voltando dois diretórios, temos a nossa base. Vamos chamar service e, em seguida, a authService.

Após importá-la, criaremos uma instância const authService = new AuthService(). Com isso, conseguimos acessar a função de cadastrarUsuario().

Em it(), vamos criar a segunda etapa do teste. Faremos const usuarioSalvo igual à authService.cadastrarUsuario(), passando os dados de usuarioMock.

Por fim, vamos para a última etapa do teste, que é fazer a comparação entre o Mock e o envio para a service. Para isso, vamos usar o expect().

Em uma nova linha, faremos await expect(), passando o usuarioSalvo. Já que queremos testar um retorno de erro, precisamos de uma função chamada rejects, que vai validar qualquer mensagem de erro. Em seguida, chamaremos a função .toThrowError(), que recebe uma mensagem que ainda vamos verificar.

Como usamos um await, devemos passar a função como assíncrona. Por isso, escrevemos async antes de () => {}.

import AuthService from '../../services/authService';

const authService = new AuthService();

describe('Testando a authService.cadastrarUsuario', () => {
  it('O usuario deve possuir um nome, email e senha', async () => {
    const usuarioMock = {
      nome: 'Raphael',
      email: 'raphael@teste.com.br'
    };

    const usuarioSalvo = authService.cadastrarUsuario(usuarioMock);

    await expect(usuarioSalvo).rejects.toThrowError('');
  });
});

Vamos agora na authService para validar e verificar qual é essa mensagem. Se observarmos, a service não possui nenhuma validação para os campos obrigatórios. Portanto, identificamos uma melhoria para o código. Agora, precisaremos refatorá-lo para que atenda a esse requisito dessa regra de negócio.

Portanto, vamos no início de cadastrarUsuario() para adicionar uma validação com if.

Se não houver o data.senha, queremos que retorne uma mensagem de erro, uma exceção. Então, vamos dar um Throw new Error(), passando qual será a mensagem de exceção que esperamos receber. Nesse caso, vamos passar a string A senha de usuário é obrigatória!.

Agora, vamos copiar essa mensagem, porque como vamos fazer um teste, essa mensagem tem que ser exatamente igual.

Também vamos aproveitar para fazer uma melhoria. Vamos selecionar o trecho de código do ìf ao const usuario e colocá-lo dentro do Try-Catch. Assim, vai estar resguardado pela validação. Ele vai tentar, caso dê algum erro, já cai no catch.

authService.js:

class AuthService {

  // código omitido…

  async cadastrarUsuario (data) {
    try {
      if (!data.senha) {
        throw new Error('A senha de usuário é obrigatória!');
      }
      data.senha = await bcryptjs.hash (data.senha, 8);

      const usuario = new Usuario (data);
      const resposta = await usuario.salvar (usuario);
      return { message: 'usuario criado', content: resposta };
    } catch (err) {
      throw new Error(err.message);
    }
  }
}

Com isso, voltamos para o authService.test.js para colar a mensagem no toThrowError().

await expect(usuarioSalvo).rejects.toThrowError('A senha de usuário é obrigatória!');

Execução do teste

Agora que temos a validação, o teste está finalizado. Como vamos rodar esse teste? Vamos precisar executá-lo.

Então, vamos abrir o package.json e conferir os comandos que já temos nesse projeto. Dentro de scripts, já tem alguns comandos que utilizamos anteriormente. Vamos verificar qual será o novo comando.

Para isso, já preparamos esse comando. Após esse test:coverage, vamos acrescentar uma vírgula e adicionar o novo cenário de teste test:auth:service:

package.json:

"scripts": {
  "dev": "nodemon server.js",
  "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
  "test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watchAll",
  "test:coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watchAll --coverage",
  "test:auth:service": "node --experimental-vm-modules node_modules/jest/bin/jest.js --testPathPattern=src/test/services/authService.test.js"
},

Esse novo cenário de teste está executando da mesma forma que os anteriores e está passando o path (caminho) do arquivo. Assim, vamos executar apenas o teste que acabamos de criar.

Agora, vamos abrir o terminal integrado com "Ctrl + `" para executar esse novo comando. Primeiro, vamos dar um npm run test:auth:service e esperar para conferir o que vai retornado do teste.

npm run test:auth:service

PASS src/test/services/authService.test.js

Testanto a authService.cadastrarUsuario

O usuário deve possuir um nome, email e senha (15 ms)

O teste funcionou perfeitamente. Podemos fechar o terminal.

Padrão Triple A

Vamos conferir estrutura que utilizamos e o padrão que criamos no teste. Em authService.test,js, conseguimos notar algumas características.

Primeiro, vamos apertar "Ctrl + Shift + P" para executar o EsLint para indentar e corrigir problemas no teste.

Conseguimos perceber que temos três etapas. A primeira é chamada de Arrange, porque é onde vamos dizer quais são as informações que queremos validar.

A segunda etapa é chamada de Act, onde vamos buscar as informações na função, no service, ou em qualquer outra função que você deseja testar.

Por último, temos uma etapa que se chama de Assert, onde vamos comparar o Arrange com o Assert.

Colocamos comentários para delimitar cada etapa.

authService.test.js

import AuthService from '../../services/authService';

const authService = new AuthService();

describe('Testando a authService.cadastrarUsuario', () => {
  // arrange
  it('O usuario deve possuir um nome, email e senha', async () => {
    const usuarioMock = {
      nome: 'Raphael',
      email: 'raphael@teste.com.br'
    };

    // act
    const usuarioSalvo = authService.cadastrarUsuario(usuarioMock);

    // assert
    await expect(usuarioSalvo).rejects.toThrowError('A senha de usuário é obrigatória!');
  });
});

Assim, acabamos de criar um padrão conhecido como padrão Triple A. Desse modo, criamos um teste muito mais fácil de ser entendido e qualquer nova pessoa que for verificar o teste, vai entendê-lo com mais facilidade.

Agora que conhecemos o padrão Triple A, podemos desenvolver novos testes seguindo esse padrão para facilitar o desenvolvimento de testes.

No próximo vídeo, vamos aprender como classificar testes e como esses testes são analisados por outras pessoas desenvolvedoras. Até o próximo vídeo.

Boas práticas com testes unitários - Classificando testes de caixa branca

Vamos continuar com o curso de testes com Node.js. No último vídeo, realizamos a criação do nosso primeiro teste.

Esse primeiro teste seguiu um padrão específico conhecido como padrão Triple A, que consiste em três etapas. No projeto no VS Code, vamos analisar o teste em authService.test.js. Nele, fizemos as separações do Arrange, Act e Assert.

Mas, como esse teste é classificado? Quais são as características que podemos atribuir a esse teste?

Analisando o teste, percebemos que ele está verificando uma funcionalidade, uma função. Por isso, ele é conhecido como um teste unitário, pois vai testar uma pequena parte do código. Nesse caso, é a função de cadastrarUsuario().

No entanto, ele possui outras classificações e características. Quais são elas?

O teste tem acesso diretamente ao código, ao service. Assim, vamos abrir o arquivo authService.js. Dentro dele, notamos que o teste unitário está sendo validado diretamente no código e obtivemos acesso para fazer uma melhoria.

Portanto, como podemos classificar esse teste? Ele pode ser classificado como um teste de caixa-branca.

Se levarmos em consideração os termos "caixa-preta" ou "caixa-branca", lembramos do aparelho dos aviões que guardam algumas informações do voo. Nesse caso, como é uma caixa-branca, temos acesso direto a essas informações do código.

Qual é a importância de ter acesso direto ao código? Temos a possibilidade de melhoria do código, uma vez que ao ter acesso ao código, conseguimos refatorar e melhorar sua qualidade.

Essa são algumas das classificações para testes. No próximo vídeo, vamos conhecer um pouco mais sobre outras classificações de testes.

Sobre o curso Node.js: implementando testes em uma API Rest

O curso Node.js: implementando testes em uma API Rest possui 82 minutos de vídeos, em um total de 41 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:

Aprenda Node.JS acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas