Casi nadie escribe tests en sus primeros meses programando, y es comprensible: cuando el código apenas funciona, verificarlo con más código parece un lujo. El problema aparece más tarde, cuando un cambio pequeño rompe algo que "ya funcionaba" y no te enteras hasta que lo ve un usuario. Los tests automáticos existen precisamente para eso: para que te enteres tú, en segundos, antes de subir nada.
Jest es el framework de testing más usado en el ecosistema JavaScript. Viene con casi todo integrado —ejecutor de tests, aserciones, mocks, cobertura— así que no necesitas instalar diez paquetes distintos para empezar. Si ya sabes montar un servidor con Node.js y Express, tienes todo lo necesario para dar el siguiente paso: comprobar que ese servidor sigue haciendo lo que dice hacer.
¿Qué es exactamente un test unitario?
Un test unitario comprueba una pieza pequeña y aislada de tu código —normalmente una función— dándole unas entradas conocidas y verificando que la salida es la esperada. No prueba toda la aplicación, no necesita base de datos ni red: solo esa función, sola, bajo control.
La idea central es simple: escribes una función, escribes un test que le da entradas conocidas y compruebas que el resultado es el que esperas. Si en el futuro cambias esa función y rompes algo, el test falla y te avisa antes de que lo descubras en producción.
Instalar Jest en tu proyecto
Si ya tienes un proyecto de Node.js con su package.json, instalar Jest es una sola línea. Si partes de cero, primero inicializa el proyecto:
npm init -y
npm install --save-dev jest
Después añade un script en el package.json para poder lanzar los tests con npm test en lugar de recordar el comando completo cada vez:
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
}
Escribe la función que vas a testear
Antes de escribir un test necesitas algo que testear. Vamos a usar un ejemplo realista: una función que calcula el total de un carrito de compra, aplicando un descuento si se supera cierto importe.
// carrito.js
function calcularTotal(precios, codigoDescuento) {
if (!Array.isArray(precios) || precios.length === 0) {
throw new Error('El carrito no puede estar vacío');
}
const subtotal = precios.reduce((acc, precio) => acc + precio, 0);
const tieneDescuento = codigoDescuento === 'DEVPULSO10';
const total = tieneDescuento ? subtotal * 0.9 : subtotal;
return Math.round(total * 100) / 100;
}
module.exports = { calcularTotal };
Escribe tu primer test con describe, it y expect
Jest organiza los tests en bloques describe (agrupan tests relacionados) que contienen bloques it o test (cada caso concreto). Dentro de cada caso, expect() es donde comparas el resultado real con el esperado.
// carrito.test.js
const { calcularTotal } = require('./carrito');
describe('calcularTotal', () => {
it('suma los precios sin descuento', () => {
const total = calcularTotal([10, 20, 30]);
expect(total).toBe(60);
});
it('aplica el 10% de descuento con el código correcto', () => {
const total = calcularTotal([100], 'DEVPULSO10');
expect(total).toBe(90);
});
it('lanza un error si el carrito está vacío', () => {
expect(() => calcularTotal([])).toThrow('El carrito no puede estar vacío');
});
});
Ejecuta npm test y Jest buscará automáticamente todos los archivos que terminen en .test.js (o estén dentro de una carpeta __tests__), los ejecutará y te mostrará en verde los que pasan y en rojo los que fallan, con el motivo exacto del fallo.
Los matchers de expect() que más vas a usar
Jest incluye decenas de "matchers" —los métodos que cuelgan de expect()— pero en el día a día usarás sobre todo un puñado de ellos. Esta tabla resume los más habituales y cuándo tiene sentido cada uno:
| Matcher | Cuándo usarlo | Ejemplo |
|---|---|---|
toBe | Comparar primitivos (números, strings, booleanos) | expect(2 + 2).toBe(4) |
toEqual | Comparar objetos o arrays por su contenido | expect(obj).toEqual({ id: 1 }) |
toContain | Comprobar que un array o string contiene un valor | expect(lista).toContain('js') |
toThrow | Comprobar que una función lanza un error | expect(fn).toThrow() |
toBeNull | Comprobar que un valor es exactamente null | expect(valor).toBeNull() |
toBeGreaterThan | Comparaciones numéricas | expect(total).toBeGreaterThan(0) |
Mocks: simular lo que no quieres ejecutar de verdad
Muchas funciones dependen de cosas externas: una petición a una API, la hora actual, una base de datos. En un test unitario no quieres depender de eso —sería lento, poco fiable y a veces imposible en un entorno de integración continua. Para eso existen los mocks: funciones falsas que sustituyen a las reales y devuelven lo que tú decidas.
const enviarNotificacion = jest.fn();
function confirmarCompra(total) {
enviarNotificacion(`Compra confirmada por ${total}€`);
return true;
}
test('confirmarCompra llama a enviarNotificacion una vez', () => {
confirmarCompra(90);
expect(enviarNotificacion).toHaveBeenCalledTimes(1);
expect(enviarNotificacion).toHaveBeenCalledWith('Compra confirmada por 90€');
});
Esto es especialmente útil si consumes una API REST desde tu aplicación: en los tests puedes simular la respuesta de esa API sin hacer ninguna petición real, y comprobar que tu código la procesa correctamente.
Cobertura de código: ¿cuánto es "suficiente"?
Ejecuta npm run test:coverage y Jest generará un informe que indica qué porcentaje de tus líneas, funciones y ramas condicionales están cubiertas por al menos un test. Es una métrica útil para detectar zonas totalmente ignoradas, pero perseguir el 100% suele ser contraproducente: acabas testeando cosas triviales solo para subir el número, mientras la lógica realmente delicada sigue mal cubierta.
Una regla más razonable: prioriza cubrir bien la lógica de negocio —cálculos, validaciones, condiciones— y no te obsesiones con testear código puramente decorativo. El mismo criterio que aplicarías siguiendo los principios de Clean Code: el esfuerzo debe ir donde hay riesgo real, no donde es fácil sumar líneas cubiertas.
Checklist: tu primera suite de tests con Jest
Márcalos a medida que los completes
0 / 8 completadosErrores frecuentes al empezar con testing
El error más común es escribir tests que solo comprueban el "camino feliz" —todo funciona como debería— y olvidar los casos límite: listas vacías, valores negativos, parámetros que faltan. Un test que solo confirma lo obvio da una falsa sensación de seguridad.
El segundo error habitual es testear detalles de implementación en lugar de comportamiento: si cambias cómo calculas algo internamente pero el resultado sigue siendo correcto, el test no debería romperse. Testea qué hace tu función, no cómo lo hace por dentro. Con esos dos hábitos corregidos, ya tienes una base de testing más sólida que la de mucha gente con años escribiendo código sin tests.