No description
Find a file
2025-09-05 14:46:27 -04:00
README.md initial commit 2025-09-05 14:46:27 -04:00

Guia de Testes em Node.js com Jest

Este guia apresenta os principais tipos de testes para uma aplicação Node.js, utilizando o framework Jest. Aqui você encontrará a estrutura básica (esqueleto) para cada tipo de teste, um guia sobre como "mockar" (simular) dependências e uma referência das principais funções de asserção do Jest com exemplos de código.

Tipos de Testes

Em uma aplicação Node.js, os testes são cruciais para garantir a qualidade, manutenibilidade e o comportamento esperado do código. Os principais tipos de testes são:

  1. Testes Unitários: Focam em testar a menor parte isolada de um sistema (uma função, um método ou uma classe) sem suas dependências externas.
  2. Testes de Integração: Verificam se diferentes partes do sistema funcionam corretamente quando combinadas (ex: Service com Repository real).
  3. Testes de Endpoint (E2E - End-to-End): Testam o fluxo completo da aplicação, desde a requisição HTTP até a resposta, simulando o comportamento de um usuário real.

Esqueleto de um Teste Unitário

Testes unitários são a base da pirâmide de testes. Eles são rápidos, baratos e essenciais para garantir a lógica de negócio.

Cenário: Testar o UsuarioService, que depende do UsuarioRepository, para garantir que a lógica de criação de usuário funciona isoladamente.

Arquivo a ser testado: src/services/UsuarioService.js

<!-- @import "[TOC]" {cmd="toc" depthFrom=1 depthTo=6 orderedList=false} -->

// src/services/UsuarioService.js
import UsuarioRepository from '../repositories/UsuarioRepository.js';
import bcrypt from 'bcrypt';

class UsuarioService {
    constructor() {
        this.repository = new UsuarioRepository();
    }

    async criar(parsedData) {
        const emailJaExiste = await this.repository.buscarPorEmail(parsedData.email);
        if (emailJaExiste) {
            throw new Error('Email já está em uso.');
        }
        const saltRounds = 10;
        parsedData.senha = await bcrypt.hash(parsedData.senha, saltRounds);
        return this.repository.criar(parsedData);
    }
}

export default UsuarioService;

Arquivo de teste: tests/unit/services/UsuarioService.test.js

import UsuarioService from '../../../services/UsuarioService.js';
import UsuarioRepository from '../../../repositories/UsuarioRepository.js';
import bcrypt from 'bcrypt';

// 1. Mock do Repository para isolar o Service das chamadas ao banco de dados
jest.mock('../../../repositories/UsuarioRepository.js');
// Mock do bcrypt para não executar a criptografia real (que é lenta)
jest.mock('bcrypt');

describe('UsuarioService - Testes Unitários', () => {
  let service;
  let repositoryMock;

  // 2. Roda antes de cada teste para garantir um estado limpo
  beforeEach(() => {
    // Cria uma nova instância do mock do repositório para cada teste
    repositoryMock = new UsuarioRepository();
    // Injeta a dependência mockada no serviço
    service = new UsuarioService();
    service.repository = repositoryMock;

    // Limpa o histórico de chamadas dos mocks
    jest.clearAllMocks();
  });

  // 3. Teste individual com `it`
  it('Deve criar um usuário com sucesso, criptografando a senha', async () => {
    // Arrange (Organização dos dados e mocks)
    const dadosUsuario = { nome: 'John Doe', email: 'john.doe@example.com', senha: 'Password123' };
    const senhaHasheada = 'senha_criptografada';

    // Configura o retorno dos mocks
    repositoryMock.buscarPorEmail.mockResolvedValue(null); // Simula que o email não existe
    bcrypt.hash.mockResolvedValue(senhaHasheada); // Simula a criptografia da senha
    repositoryMock.criar.mockResolvedValue({ _id: 'mockId', ...dadosUsuario, senha: senhaHasheada });

    // Act (Execução da função a ser testada)
    const resultado = await service.criar(dadosUsuario);

    // Assert (Verificação dos resultados)
    expect(resultado).toBeDefined();
    expect(resultado.nome).toBe('John Doe');
    expect(bcrypt.hash).toHaveBeenCalledWith('Password123', 10); // Verifica se o hash foi chamado corretamente
    expect(repositoryMock.criar).toHaveBeenCalledWith(expect.objectContaining({
      senha: senhaHasheada,
    }));
  });

  it('Deve lançar um erro se o email já estiver em uso', async () => {
    // Arrange
    const dadosUsuario = { nome: 'Jane Doe', email: 'jane.doe@example.com', senha: 'Password123' };
    repositoryMock.buscarPorEmail.mockResolvedValue({ _id: 'existenteId', ...dadosUsuario }); // Simula que o email já existe

    // Act & Assert
    // `expect().rejects` é a forma correta de testar a rejeição de uma Promise (funções async)
    await expect(service.criar(dadosUsuario)).rejects.toThrow('Email já está em uso.');

    // Garante que a função de criar não foi chamada se a validação falhou
    expect(repositoryMock.criar).not.toHaveBeenCalled();
  });
});

Esqueleto de um Teste de Endpoint (E2E)

Testes de endpoint validam o comportamento da API do ponto de vista do cliente. Usamos supertest para simular requisições HTTP e testar todo o fluxo da rota.

Cenário: Testar a rota de criação de usuários (POST /usuarios).

Arquivo de teste: tests/routes/usuarioRoutes.test.js

import request from 'supertest';
import app from '../../app'; // Importa a instância central do Express
import Usuario from '../../models/Usuario';

// 1. Mock do Mongoose Model para isolar o teste do banco de dados
jest.mock('../../models/Usuario.js');

describe('Endpoint /usuarios', () => {
  let token;

  // 2. Bloco que roda uma vez antes de todos os testes deste describe
  beforeAll(() => {
    token = 'um-token-jwt-valido'; // Simulação de token válido
  });

  // Limpa os mocks após cada teste
  afterEach(() => {
    jest.clearAllMocks();
  });

  it('POST /usuarios - Deve criar um novo usuário e retornar status 201', async () => {
    // Arrange
    const novoUsuario = { nome: 'Test User', email: 'test@example.com', senha: 'Password123!' };
    const usuarioSalvoMock = { 
      _id: 'some-random-id', 
      ...novoUsuario, 
      toObject: () => ({ _id: 'some-random-id', ...novoUsuario }) 
    };

    Usuario.create.mockResolvedValue(usuarioSalvoMock);
    Usuario.findOne.mockResolvedValue(null);

    // Act
    const response = await request(app)
      .post('/usuarios')
      .set('Authorization', `Bearer ${token}`)
      .send(novoUsuario);

    // Assert
    expect(response.status).toBe(201);
    expect(response.body.data).toHaveProperty('_id');
    expect(response.body.data.nome).toBe('Test User');
    expect(response.body.data).not.toHaveProperty('senha');
  });

  it('POST /usuarios - Deve retornar erro 400 se os dados forem inválidos', async () => {
    const response = await request(app)
      .post('/usuarios')
      .set('Authorization', `Bearer ${token}`)
      .send({ nome: 'Test' });

    expect(response.status).toBe(400);
  });

  it('POST /usuarios - Deve retornar erro 409 se o email já existir', async () => {
    const novoUsuario = { nome: 'Test User', email: 'test@example.com', senha: 'Password123!' };
    Usuario.findOne.mockResolvedValue({ _id: 'id-existente', email: 'test@example.com' });

    const response = await request(app)
      .post('/usuarios')
      .set('Authorization', `Bearer ${token}`)
      .send(novoUsuario);

    expect(response.status).toBe(409);
  });
});

Funções de Verificação do Jest (expect)

A função expect do Jest, combinada com "matchers", permite verificar se os valores atendem a certas condições.

Matcher Descrição Exemplo de Uso
Igualdade
.toBe(valor) Verifica se o valor é estritamente igual (===). Ideal para tipos primitivos. expect(2 + 2).toBe(4);
.toEqual(valor) Verifica recursivamente a igualdade de todas as propriedades de objetos ou arrays. expect({a:1}).toEqual({a:1});
Veracidade
.toBeNull() Verifica se o valor é null. expect(null).toBeNull();
.toBeUndefined() Verifica se o valor é undefined. expect(a).toBeUndefined();
.toBeDefined() O oposto de .toBeUndefined(). expect(1).toBeDefined();
.toBeTruthy() Verifica se o valor é verdadeiro em contexto booleano. expect('hello').toBeTruthy();
.toBeFalsy() Verifica se o valor é falso em contexto booleano. expect(0).toBeFalsy();
Números
.toBeGreaterThan(num) Verifica se o valor é maior. expect(10).toBeGreaterThan(5);
.toBeLessThan(num) Verifica se o valor é menor. expect(5).toBeLessThan(10);
.toBeCloseTo(num) Comparação de ponto flutuante. expect(0.1 + 0.2).toBeCloseTo(0.3);
Strings
.toMatch(regex) Verifica se uma string corresponde a uma regex. expect('Christoph').toMatch(/stop/);
Arrays e Iteráveis
.toContain(item) Verifica se contém um item. expect(['a','b']).toContain('b');
.toHaveLength(num) Verifica o comprimento. expect([1,2,3]).toHaveLength(3);
Objetos
.toHaveProperty(chave, [valor]) Verifica se possui propriedade. expect({a:1}).toHaveProperty('a',1);
Exceções
.toThrow([erro]) Verifica se uma função lança erro. expect(() => { throw new Error('ops'); }).toThrow('ops');
Mocks
.toHaveBeenCalled() Verifica se a função foi chamada. expect(mockFn).toHaveBeenCalled();
.toHaveBeenCalledTimes(num) Verifica número de chamadas. expect(mockFn).toHaveBeenCalledTimes(2);
.toHaveBeenCalledWith(arg1, ...) Verifica se foi chamada com args específicos. expect(mockFn).toHaveBeenCalledWith('arg1');