Supertest: testando rotas HTTP
1. Introdução ao Supertest e Testes de API
Supertest é uma biblioteca de teste para Node.js que permite testar rotas HTTP de forma simples e intuitiva. Ela se integra perfeitamente com frameworks como Express e Koa, permitindo que você faça requisições HTTP programáticas sem precisar iniciar um servidor real.
Por que usar Supertest com Node.js + Express? A principal vantagem é poder testar suas rotas HTTP em isolamento, garantindo que cada endpoint funcione corretamente antes de integrar com outros componentes. Isso difere de testes unitários (que testam funções isoladas) e testes de integração (que testam múltiplos componentes juntos). Os testes de API com Supertest focam especificamente nas respostas HTTP.
Para instalar:
npm install supertest --save-dev
Recomendamos usar junto com Jest, o framework de testes mais popular para Node.js:
npm install jest --save-dev
2. Configurando o Ambiente de Teste
Vamos criar uma aplicação Express mínima. O segredo é separar a criação do app do app.listen:
// app.js
const express = require('express');
const app = express();
app.use(express.json());
app.get('/', (req, res) => {
res.status(200).json({ message: 'Hello World' });
});
module.exports = app;
// server.js
const app = require('./app');
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Estrutura de pastas recomendada:
projeto/
├── src/
│ ├── app.js
│ ├── routes/
│ └── controllers/
├── tests/
│ ├── routes/
│ └── helpers/
├── package.json
└── jest.config.js
3. Testando Rotas GET
Vamos testar uma rota GET simples:
// tests/routes/get.test.js
const request = require('supertest');
const app = require('../../src/app');
describe('GET /', () => {
it('deve retornar status 200 e mensagem Hello World', async () => {
const response = await request(app).get('/');
expect(response.status).toBe(200);
expect(response.headers['content-type']).toMatch(/json/);
expect(response.body).toEqual({ message: 'Hello World' });
});
});
Testando parâmetros de rota e query strings:
// tests/routes/users.test.js
describe('GET /users/:id', () => {
it('deve retornar usuário por ID', async () => {
const response = await request(app).get('/users/123');
expect(response.status).toBe(200);
expect(response.body.id).toBe('123');
});
it('deve filtrar por query string', async () => {
const response = await request(app)
.get('/users')
.query({ role: 'admin', active: true });
expect(response.status).toBe(200);
expect(response.body.length).toBeGreaterThan(0);
});
});
4. Testando Rotas POST, PUT e DELETE
Testando criação de recursos:
// tests/routes/post.test.js
describe('POST /users', () => {
it('deve criar um novo usuário', async () => {
const newUser = {
name: 'João Silva',
email: 'joao@email.com',
age: 30
};
const response = await request(app)
.post('/users')
.send(newUser)
.set('Content-Type', 'application/json');
expect(response.status).toBe(201);
expect(response.body).toHaveProperty('id');
expect(response.body.name).toBe('João Silva');
});
it('deve retornar 400 para dados inválidos', async () => {
const invalidUser = { name: '' };
const response = await request(app)
.post('/users')
.send(invalidUser);
expect(response.status).toBe(400);
expect(response.body.error).toMatch(/nome é obrigatório/i);
});
});
Testando atualização e exclusão:
describe('PUT /users/:id', () => {
it('deve atualizar usuário existente', async () => {
const response = await request(app)
.put('/users/123')
.send({ name: 'João Atualizado' });
expect(response.status).toBe(200);
expect(response.body.name).toBe('João Atualizado');
});
});
describe('DELETE /users/:id', () => {
it('deve excluir usuário', async () => {
const response = await request(app).delete('/users/123');
expect(response.status).toBe(204);
});
});
5. Testando Autenticação e Headers Personalizados
Simulando autenticação com JWT:
// tests/routes/auth.test.js
describe('GET /protected', () => {
it('deve acessar rota protegida com token válido', async () => {
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
const response = await request(app)
.get('/protected')
.set('Authorization', `Bearer ${token}`);
expect(response.status).toBe(200);
});
it('deve retornar 401 sem token', async () => {
const response = await request(app).get('/protected');
expect(response.status).toBe(401);
});
it('deve retornar 403 para token inválido', async () => {
const response = await request(app)
.get('/protected')
.set('Authorization', 'Bearer token_invalido');
expect(response.status).toBe(403);
});
});
Testando cookies e headers customizados:
describe('POST /login', () => {
it('deve definir cookie de sessão', async () => {
const response = await request(app)
.post('/login')
.send({ username: 'admin', password: '123' });
expect(response.headers['set-cookie']).toBeDefined();
expect(response.headers['x-request-id']).toMatch(/^[a-f0-9-]+$/);
});
});
6. Testando Upload de Arquivos e Multipart
// tests/routes/upload.test.js
const path = require('path');
describe('POST /upload', () => {
it('deve fazer upload de arquivo', async () => {
const filePath = path.join(__dirname, '../fixtures/test.txt');
const response = await request(app)
.post('/upload')
.attach('file', filePath);
expect(response.status).toBe(200);
expect(response.body.filename).toBe('test.txt');
expect(response.body.size).toBeGreaterThan(0);
});
it('deve rejeitar arquivos muito grandes', async () => {
const largeFile = Buffer.alloc(10 * 1024 * 1024); // 10MB
const response = await request(app)
.post('/upload')
.attach('file', largeFile, 'large.txt');
expect(response.status).toBe(413);
});
it('deve rejeitar formatos inválidos', async () => {
const response = await request(app)
.post('/upload')
.attach('file', Buffer.from('fake'), 'file.exe');
expect(response.status).toBe(400);
expect(response.body.error).toMatch(/formato não permitido/i);
});
});
7. Boas Práticas e Integração com Jest
Usando hooks para limpeza de dados:
// tests/routes/integration.test.js
const request = require('supertest');
const app = require('../../src/app');
const db = require('../../src/database');
describe('CRUD de usuários', () => {
beforeEach(async () => {
await db.clearUsers();
await db.seedTestData();
});
afterEach(async () => {
await db.clearUsers();
});
it('deve listar todos os usuários', async () => {
const response = await request(app).get('/users');
expect(response.status).toBe(200);
expect(response.body.length).toBe(3); // dados seed
});
it('deve criar e depois listar', async () => {
await request(app)
.post('/users')
.send({ name: 'Novo Usuário' });
const response = await request(app).get('/users');
expect(response.body.length).toBe(4);
});
});
Mockando dependências externas:
jest.mock('../../src/services/email');
const emailService = require('../../src/services/email');
describe('POST /register', () => {
it('deve enviar email de boas-vindas', async () => {
emailService.sendWelcome.mockResolvedValue(true);
await request(app)
.post('/register')
.send({ email: 'teste@email.com' });
expect(emailService.sendWelcome).toHaveBeenCalledWith('teste@email.com');
});
});
8. Debugando e Otimizando Testes com Supertest
Lidando com timeouts:
describe('GET /slow-endpoint', () => {
jest.setTimeout(10000); // 10 segundos
it('deve processar requisição lenta', async () => {
const response = await request(app)
.get('/slow-endpoint')
.timeout(5000); // timeout específico da requisição
expect(response.status).toBe(200);
});
});
Debugging com logs condicionais:
it('deve retornar dados corretos', async () => {
const response = await request(app).get('/complex-endpoint');
if (process.env.DEBUG_TESTS) {
console.log('Response body:', JSON.stringify(response.body, null, 2));
console.log('Headers:', response.headers);
}
expect(response.status).toBe(200);
});
Dicas para manter testes rápidos:
- Use testes paralelos com --runInBand quando necessário
- Evite fazer chamadas reais a APIs externas
- Utilize bancos de dados em memória (SQLite, MongoDB Memory Server)
- Mantenha os testes focados em uma única responsabilidade
- Use beforeAll para configurações pesadas (criar tabelas, popular dados)
Referências
- Documentação oficial do Supertest — Repositório oficial com exemplos completos e guia de uso da biblioteca
- Testando APIs Node.js com Jest e Supertest — Tutorial prático no Dev.to cobrindo desde a instalação até testes avançados
- Express Testing com Supertest — Guia oficial do Express sobre testes, incluindo exemplos com Supertest
- Jest + Supertest: Guia Completo — Artigo da Alura abordando configuração, mocks e boas práticas
- Testando Rotas Autenticadas com Supertest — Artigo no Medium focado em autenticação JWT e headers personalizados
- Supertest Cookbook: Exemplos Práticos — Exemplos oficiais de uso do Supertest em diferentes cenários
- Integração Contínua com Testes de API — Guia da Cypress sobre como integrar testes de API em pipelines CI/CD