Supertest e integración HTTP
Supertest envía solicitudes HTTP a aplicaciones Express y Fastify en el mismo proceso sin vincular un puerto, lo que hace que las pruebas de integración sean rápidas y seguras para la ejecución en paralelo.
Receta
Tarjeta de receta de referencia rápida: lista para copiar y pegar.
import request from "supertest";
import { createApp } from "../src/app.js";
const res = await request(createApp()).get("/health");npm install -D supertest @types/supertestCuándo usarlo:
- Probar rutas, orden de middleware y códigos de estado.
- Validar cuerpos de solicitud/respuesta JSON y encabezados de autenticación.
- Evitar colisiones de puertos inestables en CI paralelo.
Ejemplo de trabajo
// src/app.ts
import express from "express";
export function createApp() {
const app = express();
app.use(express.json());
app.get("/health", (_req, res) => {
res.json({ status: "ok" });
});
app.post("/items", (req, res) => {
if (!req.body?.name) {
return res.status(400).json({ error: "name required" });
}
res.status(201).json({ id: "item_1", name: req.body.name });
});
return app;
}// test/app.http.test.ts
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import request from "supertest";
import { createApp } from "../src/app.js";
describe("HTTP API", () => {
it("GET /health returns ok", async () => {
const res = await request(createApp()).get("/health");
assert.equal(res.status, 200);
assert.deepEqual(res.body, { status: "ok" });
});
it("POST /items validates body", async () => {
const res = await request(createApp())
.post("/items")
.send({})
.set("Content-Type", "application/json");
assert.equal(res.status, 400);
assert.equal(res.body.error, "name required");
});
});Lo que esto demuestra:
- La fábrica
createApp()evita los efectos secundarios delisten(). - Supertest funciona con
node:test(no se requiere Jest). - Afirma sobre
res.bodyJSON analizado, no cadenas sin procesar.
Análisis detallado
Cómo funciona
- Supertest envuelve el servidor HTTP de Node e inyecta solicitudes a través de un socket interno.
- La ausencia de un puerto de red significa que las pruebas se ejecutan en paralelo sin
EADDRINUSE. - La pila de middleware se ejecuta de forma idéntica a la producción hasta que se simulan las dependencias.
- Fastify: usa
app.inject()de forma nativa o Supertest después deapp.ready().
Alternativa de inyección de Fastify
import Fastify from "fastify";
const app = Fastify();
app.get("/health", async () => ({ ok: true }));
await app.ready();
const res = await app.inject({ method: "GET", url: "/health" });
// res.statusCode, res.json()Notas de TypeScript
- Los tipos de Express 5 se envían con el paquete en pilas de Node 24.
- Escribe el retorno de
createApp()comoexpress.ApplicationoFastifyInstancepara obtener ayuda del editor.
Errores comunes
- Llamar a listen() en el módulo de la aplicación - El puerto se vincula al importar; las pruebas luchan contra el servidor de producción. Solución: exporta la fábrica; escucha solo en
server.ts. - Singleton de DB mutable compartido - Las pruebas paralelas corrompen los datos. Solución: reversión de transacciones por prueba o repositorio en memoria en pruebas HTTP unitarias.
- Falta de await en la solicitud - Supertest devuelve Test; debe
awaitla promesa en pruebas asíncronas. Solución: siempreawait request(...).get(). - Fuga de estado de cookies/sesiones - El agente persiste las cookies entre llamadas sin intención. Solución:
request.agent(app)solo al probar sesiones;request(app)nuevo de lo contrario. - Probar solo el camino feliz - Errores 500 sin manejar. Solución: pruebas basadas en tablas para casos 400/401/404/409.
Alternativas
| Alternativa | Usar cuándo | No usar cuándo |
|---|---|---|
Fastify inject | Código base solo de Fastify | Aplicaciones Express |
| Puerto real + fetch | Probar TLS o HTTP/2 edge | Integración predeterminada |
| Pruebas de contrato Pact | Límite consumidor-proveedor | API monolítica única |
Preguntas frecuentes
¿Supertest funciona con Express 5?
Sí. Supertest se adjunta al manejador HTTP de Application de Express como en Express 4.
¿Cómo pruebo rutas autenticadas?
await request(app).get("/me").set("Authorization", "Bearer test-token");Usa tokens de prueba o anula el middleware de autenticación en createApp({ auth: "test" }).
¿Puedo probar cargas multipart?
Supertest admite .attach() para datos de formulario multipart.
¿Cómo pruebo WebSockets?
Supertest es solo HTTP. Usa el cliente ws contra un servidor de prueba o utilidades de prueba de WS dedicadas.
¿NestJS e2e?
@nestjs/testing crea la aplicación; usa Supertest request(app.getHttpServer()).
¿Por qué no acceder a localhost:3000?
La vinculación de puertos es más lenta, inestable en CI paralelo y requiere una gestión separada del ciclo de vida del proceso.
¿Cómo simulo la base de datos?
Inyecta simulacros de repositorio al llamar a createApp({ repos: mocks }) - evita simular solo en la capa HTTP.
¿Importa el orden del middleware?
Sí, las pruebas de integración detectan errores de autenticación antes del analizador. Supertest ejercita la pila completa.
¿Cómo probar respuestas de streaming?
Afirma sobre res.text o eventos de stream; más complejo, considera pruebas unitarias enfocadas en manejadores de stream.
¿Supertest con node:test o Vitest?
Ambos funcionan; elige el ejecutor estándar del repositorio; la API de Supertest no cambia.
Relacionado
- Conceptos básicos de prueba - ubicación de la pirámide para pruebas HTTP
- Testcontainers - DB real detrás de la capa HTTP
- Vitest - ejecutor con ergonomía de simulacros
Versiones de la pila: Esta página fue escrita para Node.js 24.18.0 (LTS activa), npm 10+, TypeScript 5.6+, Express 5, Fastify 5 y NestJS 11.