| README.md | ||
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:
- 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.
- Testes de Integração: Verificam se diferentes partes do sistema funcionam corretamente quando combinadas (ex: Service com Repository real).
- 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'); |