Testes em JavaScript: Unit, Integration, E2E — do A ao Z
O desenvolvimento de aplicações web modernas em JavaScript (especialmente em Node.js) é impossível sem testes de qualidade. Os testes não apenas capturam bugs antes de irem para produção, mas também servem como documentação viva e permitem refatorar código sem medo de quebrar algo. Neste artigo, vamos abordar os três níveis principais da pirâmide de testes: unit (unitários), integration (integração) e e2e (ponta a ponta). Você aprenderá como eles diferem, quais ferramentas usar e verá exemplos prontos em Node.js.
1. Testes Unitários: testando módulos isolados
Testes unitários são a base da pirâmide. Eles verificam o funcionamento de funções, classes ou componentes individuais em completo isolamento de dependências externas (banco de dados, API, sistema de arquivos). O objetivo principal é garantir que a lógica do módulo funcione corretamente com qualquer entrada de dados.
Para Node.js, o framework mais popular é o Jest (do Facebook). Ele não requer configuração complexa, possui asserção embutida, mocks e cobertura de código.
Exemplo: testando uma função de ordenação de array
// utils.jsfunction sortNumbers(arr) { if (!Array.isArray(arr)) { throw new Error('Input must be an array'); } return [...arr].sort((a, b) => a - b);}
module.exports = { sortNumbers };// utils.test.jsconst { sortNumbers } = require('./utils');
describe('sortNumbers', () => { test('should sort an array of numbers in ascending order', () => { expect(sortNumbers([3, 1, 2])).toEqual([1, 2, 3]); });
test('should handle an empty array', () => { expect(sortNumbers([])).toEqual([]); });
test('should throw error for non-array input', () => { expect(() => sortNumbers('string')).toThrow('Input must be an array'); });});Melhores práticas para testes unitários:
- Um teste — uma verificação (um assert).
- Use
describepara agrupar testes por funções. - Mocke dependências externas (com
jest.mock()). - Escreva testes antes de escrever o código (TDD — Test-Driven Development).
2. Testes de Integração: verificando a interação entre componentes
Testes de integração verificam como diferentes módulos do sistema trabalham juntos. Por exemplo, como seu controlador interage com o banco de dados ou uma API externa. Ao contrário dos testes unitários, aqui não mockamos tudo, mas usamos ambientes reais (ou de teste).
Para testar servidores HTTP em Express, a biblioteca Supertest em conjunto com Jest é excelente.
Exemplo: testando um endpoint REST
// app.jsconst express = require('express');const app = express();
app.get('/api/hello', (req, res) => { res.json({ message: 'Hello, World!' });});
app.post('/api/data', (req, res) => { const { name } = req.body; if (!name) { return res.status(400).json({ error: 'Name is required' }); } res.status(201).json({ received: name });});
module.exports = app;// app.test.jsconst request = require('supertest');const app = require('./app');
describe('Integration tests for API', () => { test('GET /api/hello should return a greeting', async () => { const response = await request(app) .get('/api/hello') .expect(200);
expect(response.body).toEqual({ message: 'Hello, World!' }); });
test('POST /api/data with valid body should return 201', async () => { const response = await request(app) .post('/api/data') .send({ name: 'Alice' }) .expect(201);
expect(response.body).toEqual({ received: 'Alice' }); });
test('POST /api/data without name should return 400', async () => {