Kodokon kodokon.com

Probar tu API: node:test, fetch y una base de datos de pruebas

Prueba tus rutas de extremo a extremo con node:test y fetch en un puerto efímero, con cada prueba recibiendo su propia base de datos SQLite en memoria.

9 min · 3 preguntas

Abrir esta lección en Kodokon

Node 20 incluye todo lo que necesitas: node:test como runner, node:assert/strict para las aserciones, el fetch global como cliente HTTP. Cero dependencias de prueba. Y aquí es donde la arquitectura de la primera lección da sus frutos: createApp({ db }) acepta una base de datos inyectada, así que cada prueba puede montar una API completa y aislada en unos pocos milisegundos.

BASH
node --test
node --test --watch
El runner descubre por sí solo los archivos *.test.js del proyecto.

Para probar las rutas de extremo a extremo, no hace falta simular Express: monta un servidor real en el puerto 0 (el sistema asigna un puerto libre) y luego atácalo con fetch. Ejercitas exactamente lo que verá un cliente real: enrutamiento, middlewares, serialización, códigos de estado. Es el espíritu de supertest, sin la dependencia.

JAVASCRIPT
import { once } from 'node:events';

export async function startTestServer(app) {
  const server = app.listen(0);
  await once(server, 'listening');
  const { port } = server.address();
  const url = `http://127.0.0.1:${port}`;
  const close = () =>
    new Promise((resolve) => {
      server.close(resolve);
    });
  return { url, close };
}
helpers.js: un servidor real en un puerto asignado por el SO.

La base de datos de pruebas: SQLite en memoria (:memory:). Cada prueba construye su propia base de datos nueva y en blanco mediante createDb, que aplica el esquema, y luego la descarta al salir: sin datos compartidos entre pruebas, sin script de limpieza. t.after garantiza que el servidor se cierre incluso cuando una aserción falla.

JAVASCRIPT
import test from 'node:test';
import assert from 'node:assert/strict';
import { createDb } from '../src/db/connection.js';
import { createApp } from '../src/app.js';
import { startTestServer } from './helpers.js';

test('POST /api/users creates a user', async (t) => {
  const db = createDb(':memory:');
  const app = createApp({ db });
  const server = await startTestServer(app);
  t.after(() => server.close());

  const res = await fetch(`${server.url}/api/users`, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({
      email: 'ada@example.com',
      name: 'Ada',
    }),
  });

  assert.equal(res.status, 201);
  const body = await res.json();
  assert.equal(body.email, 'ada@example.com');
});
Una prueba de extremo a extremo: pila HTTP real, base de datos desechable.

Prueba de conocimientos

Comprueba que has retenido los puntos clave de esta lección.

  1. ¿Por qué arrancar el servidor de pruebas con app.listen(0)?
    • El puerto 0 desactiva la red y acelera las pruebas
    • El sistema asigna un puerto libre: las pruebas en paralelo nunca se pelean por el mismo puerto
    • Es el único puerto permitido fuera de producción
    • El puerto 0 fuerza a Express a entrar en modo de pruebas
  2. ¿Cuál es la ventaja decisiva de una base de datos :memory: creada en cada prueba?
    • Persiste los datos entre ejecuciones para poder compararlos
    • Cada prueba parte de un estado en blanco y aislado, sin script de limpieza ni interferencias entre pruebas
    • Desactiva las restricciones SQL para simplificar las inserciones
  3. ¿Por qué cerrar el servidor en t.after en lugar de al final del cuerpo de la prueba?
    • t.after se ejecuta aunque una aserción falle, evitando servidores huérfanos que impiden que el proceso termine
    • t.after se ejecuta antes de la prueba, lo que prepara el servidor
    • Es puramente estético, el comportamiento es idéntico