Por qué probar el backend
Un test automático es código que ejecuta tu código y falla si el comportamiento no es el esperado. En el backend nos protege de romper endpoints, reglas de negocio o contratos de datos al refactorizar.
Se distinguen dos grandes familias:
- Pruebas unitarias (unit): prueban una pieza aislada (un servicio, una función pura, un handler) sin tocar red, disco ni base de datos. Son rápidas y precisas: si fallan, sabes exactamente dónde está el problema.
- Pruebas de integración (integration): ejercitan varias piezas juntas, típicamente una petición HTTP real que atraviesa rutas, middleware y handlers. Son más lentas pero comprueban que todo encaja de extremo a extremo.
Aislar con dobles de prueba (mocks)
Para probar una unidad sin sus dependencias reales se usan dobles: objetos falsos que sustituyen a una dependencia (la base de datos, un servicio externo). Un mock o espía además registra cómo se le llamó, para poder afirmar sobre ello después.
// Servicio que depende de un "repositorio" (acceso a datos).
function registrarUsuario(repo, nombre) {
return repo.guardar({ nombre, creado: true });
}
// En el test, le pasamos un repo FALSO que espía las llamadas.
const llamadas = [];
const repoMock = {
guardar(usuario) { llamadas.push(usuario); return { id: 1, ...usuario }; },
};
const r = registrarUsuario(repoMock, "Ana");
// Verificamos comportamiento (no implementación interna):
// - se llamó a guardar exactamente una vez
// - con el nombre correcto
// - y devolvimos lo que el repo devolvió
La clave del mock: comprobar que se llamó correctamente (cuántas veces, con qué argumentos) sin ejecutar la dependencia real.
Probar endpoints con supertest
Para las pruebas de integración de un servidor HTTP, la librería supertest lanza la app sobre un puerto efímero y deja escribir aserciones sobre la respuesta:
import request from "supertest";
import { app } from "./app.js";
await request(app)
.get("/usuarios/1")
.expect(200)
.expect((res) => {
if (res.body.nombre !== "Ana") throw new Error("nombre incorrecto");
});
supertest se encarga de arrancar y cerrar el servidor; tú solo describes la
petición (.get, .post, .send(...)) y lo que esperas (.expect(...)).
TDD (Test-Driven Development)
El desarrollo guiado por pruebas invierte el orden habitual en un ciclo corto:
- Rojo: escribe primero un test que describe el comportamiento deseado. Falla (aún no existe el código).
- Verde: escribe el mínimo código para que el test pase.
- Refactor: limpia el código con la red de seguridad del test en verde.
TDD empuja a diseñar funciones pequeñas y testeables, y deja una suite de pruebas como subproducto natural.
Ejemplos
Un espía simple: registrar las llamadas en un array
function notificar(servicio, mensaje) {
servicio.enviar(mensaje);
}
const llamadas = [];
const servicioMock = { enviar: (m) => llamadas.push(m) };
notificar(servicioMock, "Hola");
notificar(servicioMock, "Adiós");
console.log(llamadas.length); // 2 -> se llamó dos veces
console.log(llamadas[0]); // "Hola" -> con el argumento correcto