Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Supertest no Node.js

Atualizado em: 13 de setembro de 2026

Estação de trabalho usada no desenvolvimento de aplicações Node.js

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 supertest

Se o projeto usa TypeScript, instale tipos somente quando a versão e o pacote exigirem:

npm install --save-dev @types/supertest

Separando 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.

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:integration

Em 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.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita