Realizando Testes Unitários em API com Vitest

Imagem de capa do post Realizando Testes Unitários em API com Vitest

Teste unitário é um tipo de teste de software que verifica o comportamento de uma unidade de código isoladamente. Uma unidade de código pode ser um método, uma função ou uma classe. Os testes unitários são importantes para o desenvolvimento de software por vários motivos, incluindo:

  • Garantia da qualidade: Os testes unitários ajudam a garantir que o código funcione conforme o esperado. Eles podem ajudar a identificar erros e falhas antes que eles sejam lançados no software. 

  • Redução de custos: Esse tipo de teste ajuda a reduzir os custos de desenvolvimento e manutenção de software. Ele evita que erros sejam encontrados no final do processo de desenvolvimento, quando são mais caros para corrigir. 

  • Melhoria da produtividade: Os testes unitários também melhoram a produtividade dos desenvolvedores, ajudando-os a escrever códigos mais confiáveis e corrigindo erros mais rapidamente. 

Justificativa para a escolha do Vitest 

O Vitest é um framework de teste unitário JavaScript/TypeScript moderno e fácil de usar. Ele é baseado no Jest, mas oferece recursos adicionais, como: 

  • Testes síncronos e assíncronos: Adequado para testar código que usa APIs ou outras dependências assíncronas. 

  • Testes de integração: Testam a interação entre unidades de código. 

  • Testes de cobertura: O Vitest fornece relatórios de cobertura de código, que podem ajudar os desenvolvedores a identificar áreas de código que não estão sendo testadas. 

Por esses motivos, o Vitest é uma boa escolha para realizar testes unitários em APIs. Ele é fácil de usar, oferece recursos abrangentes e é compatível com JavaScript e TypeScript.

Criando testes para suas rotas

A API que estamos utilizando no exemplo é uma API minimalista desenvolvida com Fastify e TypeScript para gerenciar operações básicas de um serviço de usuário. A API oferece duas principais funcionalidades: listar todos os usuários cadastrados (GET /users) e criar novos usuários, evitando duplicatas por meio do nome de usuário (POST /users). A autenticação é implementada de maneira simples, exigindo um token de autorização para a criação de usuários. A lógica de negócios está encapsulada na classe UserService, que mantém os usuários em memória. Erros são tratados de forma consistente usando a classe AppError. Este exemplo fornece uma base para a implementação de testes unitários visando garantir o correto funcionamento da API em diferentes cenários.

É importante mencionar que a API de exemplo é simplificada para facilitar a compreensão dos princípios de teste. A implementação real de uma API pode envolver mais complexidade, dependendo dos requisitos específicos do projeto.

Realizar os imports necessários

  
import { test, expect, describe, beforeAll } from "vitest" 
import { UserService } from "../user.service"

Aqui estamos importando as funções de teste (test), asserções (expect), descrição (describe) e beforeAll do framework de teste "vitest". Além disso, estamos importando a classe UserService de um módulo chamado "user.service". 

Inicialização dos serviços

  
let userService: UserService;

beforeAll(() => {

   userService = new UserService();

})

Nesta parte, estamos declarando uma variável userService do tipo UserService e, no bloco beforeAll, está instanciando a classe UserService antes de executar os testes. O beforeAll é um gancho que executa uma vez antes de todos os testes no escopo do describe.

Bloco de testes

  
describe("User Service", () => { 

    // ... test cases ... 

 }); 

Aqui, estamos usando o describe para agrupar os testes relacionados ao serviço de usuário. Dentro deste bloco, você tem vários testes individuais

Teste: Deve ser possível criar um usuário

Este teste verifica se é possível criar um usuário com sucesso, usando o método create do UserService. Ele verifica se o resultado possui uma propriedade 'id' e se o nome de usuário é o esperado.

Teste Descritivo 

test('Deve ser possível criar um usuário', () => {

Esta linha define um teste com uma descrição clara e legível: "Deve ser possível criar um usuário".

Chamada do Método create

const result = userService.create({
   name: 'User Test',
   username: 'user_test'
})

Aqui, estamos chamando o método create da instância do UserService, passando um objeto de usuário como argumento. O objeto de usuário possui um nome e um nome de usuário.

Expectativas (Assertions)

 
expect(result).toHaveProperty('id') 
expect(result.username).toBe('user_test')

Estas linhas definem as expectativas ou asserções do teste: 

  • expect(result).toHaveProperty('id'): Verifica se o objeto resultante possui uma propriedade 'id'. Isso implica que o usuário foi criado com sucesso, pois um 'id' foi atribuído a ele durante o processo de criação. 

  • expect(result.username).toBe('user_test'): Garante que o nome de usuário do usuário criado seja igual a 'user_test'.

Dessa forma, esse teste específico verifica se a criação de um usuário com determinado nome e nome de usuário ocorre conforme o esperado, validando a presença de um 'id' e o nome de usuário associado ao usuário criado.

Teste: Não deve ser possível criar um usuário já existente

Este teste verifica se é lançada uma exceção quando se tenta criar um usuário com um nome de usuário que já existe. Ele usa toThrow para garantir que a exceção esperada seja lançada.

Teste descritivo

	 test('Não deve ser possível criar um usuário já existente', () => {
	

A descrição do teste é "Não deve ser possível criar um usuário já existente".

Criação de um usuário com nome de usuário único

	userService.create({ 

            name: 'User Test', 

            username: 'user_test_already_exists' 

        })
	

Nesta linha, um usuário é criado com um nome de usuário único, 'user_test_already_exists'.

Expectativa (Assertion) de exceção

	expect(() => {

           userService.create({

               name: 'User Test',

               username: 'user_test_already_exists'

           })

       }).toThrow('User already exists')

Esta parte do teste usa a função expect para verificar se a criação de um usuário com um nome de usuário que já existe gera uma exceção. O bloco de código dentro de expect(() => {...}) representa a tentativa de criar um usuário duplicado.

toThrow('User already exists'): Certifica-se de que a exceção gerada tem a mensagem "User already exists", indicando que o sistema está tratando corretamente a tentativa de criar um usuário com um nome de usuário que já está em uso.

Teste: Deve ser possível recuperar a lista de usuários

Este teste verifica se é possível obter uma lista de usuários usando o método findAll do UserService e se o resultado possui o comprimento esperado.

Teste descritivo

	 test('Deve ser possível recuperar a lista de usuários', () => { 
	

A descrição do teste é "Deve ser possível recuperar a lista de usuários".

Chamada do Método findAll

	const result = userService.findAll();

Aqui, estamos chamando o método findAll da instância do UserService. Este método é projetado para retornar a lista de todos os usuários cadastrados.

Expectativa (Assertion) de Tamanho da Lista

	expect(result).toHaveLength(2)
		
	
			

Esta linha verifica se o resultado (a lista de usuários) possui um comprimento (tamanho) esperado de 2. Isso implica que, de acordo com o contexto do teste, dois usuários foram criados e estão presentes na lista.

Testes E2E

Realizar os imports necessários

	
import { test, describe } from "vitest"
import request from "supertest";
import { app } from "../../../app";
	

Aqui estamos importando as funções de teste (test), descrição (describe) do framework de teste "vitest". Também importando a função de teste HTTP request do  “supertest”. Além disso, estamos importando a classe UserService de um módulo chamado "user.service". 

Bloco de testes

describe('User E2E', () => {

   // ... test cases ...

});

Usa o describe para agrupar os testes relacionados à interação de usuário do ponto de vista do cliente (end-to-end).

Teste: Deve ser possível criar um usuário

Este teste verifica se é possível criar um usuário com sucesso, usando o método create do UserService. Ele verifica se a rota da o código esperado.

Teste Descritivo 

	    test('Deve ser possível criar um usuário', async () => {
	 

A descrição do teste indica que o objetivo é verificar se é possível criar um usuário.

Preparação da Aplicação

await app.ready();

Antes de realizar a requisição, aguarda o momento em que a aplicação está pronta. Isso é especialmente importante em testes end-to-end para garantir que a aplicação esteja totalmente inicializada.

Solicitação POST para Criar Usuário

await request(app.server)

    .post('/users')

.set('Authorization', 'Bearer TOKEN_FAKE')

.send({

name: 'User Test E2E',

username: 'user_test_e2e'

}).expect(200)

Estas linhas definem as expectativas ou asserções do teste: 

  • expect(result).toHaveProperty('id'): Verifica se o objeto resultante possui uma propriedade 'id'. Isso implica que o usuário foi criado com sucesso, pois um 'id' foi atribuído a ele durante o processo de criação. 

  • expect(result.username).toBe('user_test'): Garante que o nome de usuário do usuário criado seja igual a 'user_test'.

Dessa forma, esse teste específico verifica se a criação de um usuário com determinado nome e nome de usuário ocorre conforme o esperado, validando a presença de um 'id' e o nome de usuário associado ao usuário criado.

A importância dos testes unitários vai além de uma simples verificação de funcionalidades, desempenhando um papel crucial na garantia da qualidade do código, na redução de custos ao evitar a identificação de erros tardiamente no desenvolvimento e na melhoria da produtividade dos programadores. O Vitest, escolhido para conduzir os testes unitários neste contexto, demonstra ser uma opção robusta, oferecendo recursos abrangentes, como suporte a testes síncronos e assíncronos, testes de integração e relatórios de cobertura de código. 

Além disso, a abordagem prática exemplificada no código de teste E2E também enfatiza a importância da validação completa do sistema. Os testes E2E, ao simular interações do usuário do ponto de vista do cliente, proporcionam uma visão abrangente do funcionamento da API. A combinação de testes unitários e E2E contribui para um processo de desenvolvimento mais confiável. Ao adotar uma abordagem abrangente de teste, os programadores podem identificar e corrigir problemas em diferentes camadas da aplicação, assegurando um software escalável e eficiente. 

Compartilhar:
0 Comentários

Deixe seu comentário

Fale AGORA com um de nossos consultores

Comentário adicionado com sucesso