O Supertest no Node.js permite testar APIs HTTP diretamente a partir da aplicação, sem precisar subir manualmente um servidor em uma porta fixa. A biblioteca recebe uma instância de servidor ou função compatível, cria uma conexão temporária quando necessário e oferece uma API fluente para enviar requisições, definir headers, enviar JSON, anexar arquivos e validar status, corpo e cookies.
Esse tipo de teste fica entre a unidade pura e o teste ponta a ponta completo. Ele exercita roteamento, middlewares, serialização, autenticação, validação e tratamento de erros, mas pode continuar usando banco e dependências controladas. O resultado é uma suíte rápida, legível e próxima do comportamento real da API.
Neste guia, você aprenderá a configurar Supertest com o Node Test Runner, testar GET, POST, autenticação, cookies, uploads, erros, sessões e integração com banco de dados, além de evitar dependência de portas e estado compartilhado.
O que é Supertest?
Supertest é uma biblioteca baseada em SuperAgent para testar servidores HTTP Node.js. O repositório oficial do Supertest explica que a ferramenta aceita um http.Server ou uma função de aplicação e, quando o servidor ainda não está ouvindo, liga automaticamente em uma porta efêmera.
Isso evita código como:
const server = app.listen(3001);
// testes
server.close();Em vez disso:
import request from 'supertest';
const response = await request(app)
.get('/health')
.expect(200);Instalação
npm install --save-dev supertestSe o projeto usa TypeScript, instale tipos somente quando a versão e o pacote exigirem:
npm install --save-dev @types/supertestSeparando app e listen
Para testar sem abrir porta fixa, separe a construção da aplicação:
// src/app.js
import express from 'express';
export function createApp() {
const app = express();
app.use(express.json());
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
return app;
}// src/server.js
import { createApp } from './app.js';
const app = createApp();
app.listen(3000);O teste importa apenas createApp(). Essa separação também facilita graceful shutdown e injeção de dependências. Veja Graceful Shutdown no Node.js e Injeção de Dependência no Node.js.
Primeiro teste com Node Test Runner
import test from 'node:test';
import assert from 'node:assert/strict';
import request from 'supertest';
import { createApp } from '../src/app.js';
test('GET /health retorna ok', async () => {
const app = createApp();
const response = await request(app)
.get('/health')
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200);
assert.deepEqual(response.body, {
status: 'ok'
});
});Consulte Node Test Runner para hooks, paralelismo e organização.
Testando POST com JSON
test('POST /users cria usuário', async () => {
const app = createApp();
const response = await request(app)
.post('/users')
.send({
name: 'Ana',
email: 'ana@example.com'
})
.set('Content-Type', 'application/json')
.expect('Content-Type', /json/)
.expect(201);
assert.equal(response.body.name, 'Ana');
assert.match(response.body.id, /^[a-z0-9-]+$/);
});.send() serializa objetos como JSON quando o content type é apropriado.
Validação de erro
test('rejeita e-mail inválido', async () => {
const response = await request(createApp())
.post('/users')
.send({ name: 'Ana', email: 'invalido' })
.expect(422);
assert.equal(response.body.code, 'VALIDATION_ERROR');
assert.ok(Array.isArray(response.body.errors));
});Não verifique apenas o status. Valide o contrato de erro, especialmente quando a API usa Problem Details no Node.js.
Assertions customizadas
function hasPagination(response) {
if (!Array.isArray(response.body.data)) {
throw new Error('data deve ser array');
}
if (!('nextCursor' in response.body)) {
throw new Error('nextCursor ausente');
}
}
await request(app)
.get('/products')
.expect(200)
.expect(hasPagination);Assertions customizadas ajudam a reutilizar contratos, mas não crie abstrações tão genéricas que escondam o cenário testado.
Autenticação Bearer
const token = createTestToken({
sub: 'user-1',
role: 'admin'
});
await request(app)
.get('/admin/users')
.set('Authorization', `Bearer ${token}`)
.expect(200);Use chaves e tokens exclusivos de teste. Nunca copie credenciais de produção. Para autenticação, veja JWT Seguro no Node.js.
Testando 401 e 403
await request(app)
.get('/admin/users')
.expect(401);
await request(app)
.get('/admin/users')
.set('Authorization', `Bearer ${userToken}`)
.expect(403);401 representa ausência ou falha de autenticação. 403 representa identidade válida sem permissão.
Cookies e sessões
request.agent() preserva cookies:
test('mantém sessão entre requisições', async () => {
const agent = request.agent(createApp());
await agent
.post('/login')
.send({ email: 'ana@example.com', password: 'test-password' })
.expect(204)
.expect('set-cookie', /session=/);
const response = await agent
.get('/me')
.expect(200);
assert.equal(response.body.email, 'ana@example.com');
});Esse padrão testa login real, cookie e rota protegida no mesmo agente.
Validando atributos do cookie
Confirme flags de segurança:
const response = await request(app)
.post('/login')
.send(credentials)
.expect(204);
const cookie = response.headers['set-cookie'][0];
assert.match(cookie, /HttpOnly/i);
assert.match(cookie, /SameSite=Lax/i);
assert.match(cookie, /Secure/i);Em ambiente local sem HTTPS, a aplicação pode configurar Secure de forma condicional. Teste a configuração de produção separadamente.
Uploads multipart
await request(app)
.post('/avatars')
.field('name', 'Avatar de teste')
.attach('file', 'test/fixtures/avatar.png')
.expect(201);Use fixtures pequenas e não inclua dados reais. Verifique tipo, tamanho máximo e rejeição de extensões indevidas.
Headers de cache e segurança
await request(app)
.get('/public/config')
.expect('Cache-Control', /max-age=60/)
.expect('X-Content-Type-Options', 'nosniff')
.expect(200);Supertest também serve para testar CORS, CSP e headers do Helmet. Consulte Helmet e CSP no Node.js.
Testando redirects
await request(app)
.get('/old-route')
.expect('Location', '/new-route')
.expect(308);Use status adequado para preservar método quando necessário.
Banco de dados real
Para testes de integração, use PostgreSQL descartável:
test.beforeEach(async () => {
await truncateTables();
await seedBaseData();
});Não compartilhe o mesmo estado entre testes paralelos sem isolamento. Testcontainers no Node.js ajuda a subir dependências reais.
Transação por teste
Uma estratégia é abrir transação e reverter ao final. Porém, se a aplicação usa pool diferente da conexão do teste, o rollback não inclui todas as queries. Nesse caso, use schema ou banco por teste, truncation controlada ou injeção explícita de conexão.
Dependências externas
Supertest testa a API local, mas chamadas a serviços externos devem ser mockadas com MSW ou Undici MockAgent:
Desative rede externa para evitar testes acessando produção.
Testes em paralelo
Crie uma aplicação por teste ou por arquivo. Não compartilhe containers mutáveis, agentes ou bancos sem estratégia. Recursos globais tornam falhas intermitentes difíceis de reproduzir.
Servidor real versus app em memória
Passar a aplicação é rápido e evita portas. Um servidor real é necessário quando você precisa testar:
- TLS;
- HTTP/2 específico;
- proxy reverso;
- limites de socket;
- graceful shutdown;
- configuração de rede.
Nesses casos, inicie em porta 0 para o sistema escolher uma porta livre.
Testando HTTP/2
Supertest suporta opção de HTTP/2 em cenários compatíveis:
request(app, { http2: true })
.get('/health')
.expect(200);Confirme suporte do framework e da versão instalada.
Snapshots
Evite snapshot completo de respostas com IDs e timestamps. Prefira assertions explícitas:
assert.equal(response.body.status, 'created');
assert.equal(typeof response.body.id, 'string');Snapshots grandes ficam frágeis e podem aprovar mudanças importantes sem revisão adequada.
OpenAPI
Combine Supertest com validação de schema OpenAPI. O teste envia a requisição e a resposta é validada contra o contrato. Veja OpenAPI com Node.js.
CI
- run: npm ci
- run: npm run typecheck
- run: npm run test:integrationEm falha, preserve logs e relatórios, mas não segredos. O artigo CI para Node.js com GitHub Actions mostra uma pipeline completa.
Erros comuns
- Subir porta fixa: testes colidem.
- Compartilhar banco: paralelismo fica instável.
- Verificar só status: contrato pode estar errado.
- Mockar tudo: o teste deixa de ser integração.
- Usar token real: risco de vazamento.
- Não fechar recursos: processo não termina.
- Snapshot gigante: mudanças importantes passam despercebidas.
- Rede externa habilitada: suíte fica não determinística.
Conclusão
O Supertest no Node.js oferece uma forma direta de testar a API como cliente HTTP, sem gerenciar portas manualmente. Ele exercita rotas, middlewares, autenticação, cookies, uploads e serialização com uma API legível.
Separe a criação da aplicação do servidor, use dependências descartáveis, bloqueie rede externa e valide o contrato completo da resposta. Com esses cuidados, Supertest produz testes de integração rápidos e confiáveis, capazes de detectar falhas que testes unitários isolados não encontram.


