Configuración para crear tests en nuestra API
Hasta ahora “funciona en mi máquina” porque la API arranca y responde, pero nos falta lo importante: saber si hace lo que se supone que tiene que hacer. Así que en esta clase empezamos a testear la API y lo hacemos de una forma muy pro: sin instalar dependencias, usando lo que ya trae Node.
También comentamos la idea de TDD (Test Driven Development). En nuestro caso no lo seguimos al pie de la letra porque el código ya existe, pero sí adoptamos la parte clave: ver tests fallar primero, arreglar lo necesario y luego refactorizar con todo “en verde”.
Herramientas nativas: node:test + assert
Node incluye un runner de tests nativo (node:test) y un módulo de aserciones (node:assert). Con eso ya podemos empezar.
Creamos un archivo de tests, por ejemplo:
a.test.js
Y dentro importamos lo básico:
import test, { describe, before, after } from 'node:test'
import assert from 'node:assert'
beforese ejecuta una vez antes de todos los tests.afterse ejecuta una vez al final para limpiar (muy importante para no dejar el puerto ocupado).
Hacer Express “testeable”
Para testear bien, necesitamos controlar cuándo se levanta el servidor. Es decir: que app.listen() no se ejecute automáticamente cuando importamos la app.
La idea es:
- Exportar la app (sin escuchar).
- Solo levantar el servidor en desarrollo o cuando lo decidamos explícitamente.
- En tests, levantar y cerrar el servidor desde
before/after.
Ejemplo de app.js (o como lo tengas):
import express from 'express'
import jobsRouter from './routes/jobs.js'
export const app = express()
app.use(express.json())
app.use('/jobs', jobsRouter)
Y en el punto de entrada (por ejemplo index.js) puedes condicionar el listen para no interferir con los tests:
import { app } from './app.js'
const PORT = process.env.PORT ?? 1234
// Evitamos levantar el servidor automáticamente en entorno "test"
if (process.env.NODE_ENV !== 'test') {
app.listen(PORT, () => {
console.log(`Servidor levantado en http://localhost:${PORT}`)
})
}
La clave es que en tests podamos decir: “yo levanto el servidor cuando me venga bien”.
Levantar y cerrar el servidor con before y after
En el test vamos a usar un puerto distinto al habitual para evitar conflictos (por ejemplo 43456) y construir una baseURL.
import test, { describe, before, after } from 'node:test'
import assert from 'node:assert'
import { app } from './app.js'
const PORT = 43456
const baseURL = `http://localhost:${PORT}`
let server
before(async () => {
// app.listen es asíncrono: resolvemos cuando el servidor está listo
await new Promise((resolve, reject) => {
server = app.listen(PORT, (err) => {
if (err) return reject(err)
resolve()
})
})
})
after(async () => {
// Cerramos el server para no dejar el puerto ocupado
await new Promise((resolve, reject) => {
server.close((err) => {
if (err) return reject(err)
resolve()
})
})
})
Usamos promesas porque levantar y cerrar el servidor no ocurre instantáneamente, y los tests deben esperar a que el servidor esté listo (y luego a que se cierre).
Escribiendo un primer test (ejemplo)
Como Node ya tiene fetch, podemos atacar endpoints tal cual lo haría un cliente.
describe('GET /jobs', () => {
test('debería devolver un JSON con los trabajos', async () => {
const res = await fetch(`${baseURL}/jobs`)
assert.equal(res.status, 200)
assert.equal(res.headers.get('content-type')?.includes('application/json'), true)
const data = await res.json()
assert.equal(Array.isArray(data), true)
})
})
Aquí estamos validando lo básico:
- Código de estado.
- Que la respuesta sea JSON.
- Que la estructura sea la esperada.
Lo que hemos aprendido
- Por qué testear la API es vital (y “funciona” no es suficiente).
- Cómo usar
node:testynode:assertsin dependencias. - Cómo evitar que el servidor se levante solo al importar la app.
- Cómo levantar y cerrar el servidor con
beforeyafterusando promesas. - Por qué usar un puerto distinto en tests para evitar conflictos.