Skip to main content
Version: Next

Guía de Jest

QUICKSTART​

  1. Instalación: npm install --save-dev jest
  2. Crear archivo de prueba: Con el nombre del módulo + .test.js (ej: cargarExcel.test.js)
  3. Importar módulo: Importar las funciones a probar en el archivo de test
  4. Configurar package.json: Asegurar que en scripts tengamos "test": "jest"
  5. Ejecutar pruebas: npm test
  6. Revisar resultados: Ver pruebas exitosas/fallidas con mensajes de error

Estructura Básica de una Prueba​

¿Qué es? Una prueba en Jest es una función que verifica si nuestro código se comporta como esperamos. Cada prueba tiene un nombre descriptivo y una función que ejecuta el código y verifica el resultado.

¿Cuándo usar test() vs describe()?

  • test() se usa para pruebas individuales
  • describe() se usa para agrupar pruebas relacionadas y organizarlas mejor
test('NOMBRE DE LA FUNCIÓN', () => {
expect('cosaProbar').toBe('resultadoEsperado');
});

// O usando describe para agrupar pruebas
describe('Calculadora', () => {
test('debe sumar correctamente', () => {
expect(2 + 2).toBe(4);
});

test('debe restar correctamente', () => {
expect(5 - 3).toBe(2);
});
});

Matchers Principales​

¿Qué son los Matchers? Son métodos que Jest proporciona para verificar que los valores cumplan ciertas condiciones. Cada matcher tiene un propósito específico y es importante elegir el correcto para cada situación.

Igualdad​

¿Cuándo usar cada uno?

  • .toBe(): Para valores primitivos (números, strings, booleans) y cuando necesitas verificar que son exactamente el mismo objeto en memoria
  • .toEqual(): Para comparar el contenido de objetos y arrays, ignorando si son el mismo objeto en memoria
  • .toStrictEqual(): Como toEqual pero más estricto con propiedades undefined
test('toBe vs toEqual', () => {
// toBe funciona con primitivos
expect(5).toBe(5);
expect('hello').toBe('hello');

// toEqual compara contenido de objetos
const obj1 = {name: 'Juan'};
const obj2 = {name: 'Juan'};
expect(obj1).toEqual(obj2); // Pasa - mismo contenido
expect(obj1).toBe(obj2); // Falla - diferentes objetos en memoria
});

Verificación de Tipos​

¿Por qué son importantes? En JavaScript, null, undefined y false pueden comportarse de manera similar en ciertas situaciones, pero a menudo necesitamos distinguir entre ellos.

test('verificaciones de tipo', () => {
let valor;
expect(valor).toBeUndefined(); // Variable declarada pero no asignada

valor = null;
expect(valor).toBeNull(); // Valor intencionalmente vacío

valor = 0;
expect(valor).toBeFalsy(); // 0 es falsy pero está definido
expect(valor).toBeDefined(); // Tiene un valor asignado

valor = 'texto';
expect(valor).toBeTruthy(); // String no vacío es truthy
});

Números​

¿Por qué .toBeCloseTo() para flotantes? Los números decimales en JavaScript pueden tener problemas de precisión debido a cómo se almacenan en memoria. Por ejemplo, 0.1 + 0.2 no es exactamente 0.3.

test('problemas con números flotantes', () => {
// Esto fallará debido a la precisión de flotantes
// expect(0.1 + 0.2).toBe(0.3);

// Esto funciona correctamente
expect(0.1 + 0.2).toBeCloseTo(0.3);
});

test('comparaciones numéricas', () => {
const edad = 25;
expect(edad).toBeGreaterThan(18); // Mayor de edad
expect(edad).toBeLessThanOrEqual(100); // Edad realista
});

Strings​

¿Cuándo usar cada método?

  • .toMatch(): Cuando necesitas verificar patrones complejos con regex o substrings
  • .toContain(): Para verificaciones simples de substring
test('validaciones de string', () => {
const email = 'usuario@ejemplo.com';

// Verificar formato de email con regex
expect(email).toMatch(/^[^\s@]+@[^\s@]+\.[^\s@]+$/);

// Verificar que contiene el dominio
expect(email).toContain('@ejemplo.com');

// Verificar que no contiene espacios
expect(email).not.toContain(' ');
});

Arrays y Objetos​

¿Para qué sirve cada matcher?

  • .toContain(): Verifica si un elemento está presente en un array
  • .toHaveLength(): Verifica el tamaño exacto
  • .toHaveProperty(): Verifica que un objeto tenga una propiedad específica
test('validaciones de arrays', () => {
const productos = ['laptop', 'mouse', 'teclado'];

expect(productos).toContain('laptop'); // Producto existe
expect(productos).toHaveLength(3); // Cantidad correcta
expect(productos).not.toContain('monitor'); // Producto no existe
});

test('validaciones de objetos', () => {
const usuario = {
id: 1,
nombre: 'Juan',
email: 'juan@test.com',
activo: true
};

expect(usuario).toHaveProperty('id'); // Tiene la propiedad
expect(usuario).toHaveProperty('email', 'juan@test.com'); // Valor específico
expect(usuario).not.toHaveProperty('password'); // No tiene contraseña expuesta
});

Negación​

¿Para qué sirve .not? Te permite verificar que algo NO ocurra, lo cual es tan importante como verificar que sí ocurra. Es especialmente útil para casos edge y validaciones de seguridad.

test('ejemplos de negación', () => {
const password = 'miPassword123';

// Verificar que la contraseña no esté vacía
expect(password).not.toBe('');
expect(password).not.toHaveLength(0);

// Verificar que no contenga información sensible
expect(password.toLowerCase()).not.toContain('password');
expect(password).not.toMatch(/123456|qwerty|admin/);
});

Pruebas Asíncronas​

¿Por qué son diferentes? El código asíncrono (como llamadas a APIs, lectura de archivos, timers) no se ejecuta inmediatamente. Jest necesita saber cuándo la operación ha terminado para poder verificar el resultado.

Promises con async/await (Recomendado)​

¿Por qué usar async/await? Es la forma más moderna y legible de manejar código asíncrono. Jest automáticamente esperará a que la promesa se resuelva.

// Función que simula una llamada a API
async function obtenerUsuario(id) {
const response = await fetch(`/api/usuarios/${id}`);
return response.json();
}

test('debe obtener usuario por ID', async () => {
// La prueba espera automáticamente a que se resuelva
const usuario = await obtenerUsuario(1);

expect(usuario).toHaveProperty('id', 1);
expect(usuario).toHaveProperty('nombre');
});

test('debe manejar errores de API', async () => {
// Verificar que se lance un error
await expect(obtenerUsuario(999)).rejects.toThrow('Usuario no encontrado');
});

Promises con .then (Alternativa)​

test('promesa con then', () => {
// IMPORTANTE: Debes retornar la promesa
return obtenerUsuario(1).then(usuario => {
expect(usuario.id).toBe(1);
});
});

Callbacks (Para código legacy)​

¿Cuándo usar? Solo cuando trabajas con código antiguo que usa callbacks en lugar de promesas.

function obtenerDatosCallback(callback) {
setTimeout(() => {
callback(null, {data: 'información'});
}, 100);
}

test('callback con done', done => {
obtenerDatosCallback((error, data) => {
try {
expect(error).toBeNull();
expect(data).toHaveProperty('data');
done(); // IMPORTANTE: Llamar done() para indicar que terminó
} catch (e) {
done(e); // Pasar el error a done() si algo falla
}
});
});

Mocks y Spies​

¿Qué son y para qué sirven? Los mocks son versiones "falsas" de funciones o módulos que podemos controlar completamente. Son esenciales para:

  • Aislar el código que estamos probando
  • Simular respuestas de APIs sin hacer llamadas reales
  • Verificar que las funciones se llamen correctamente

Mock Functions​

test('verificar llamadas a función', () => {
const mockCallback = jest.fn();

// Función que usa el callback
function procesarDatos(datos, callback) {
if (datos.length > 0) {
callback('procesado');
}
}

procesarDatos(['a', 'b'], mockCallback);

// Verificar que se llamó
expect(mockCallback).toHaveBeenCalled();
expect(mockCallback).toHaveBeenCalledWith('procesado');
expect(mockCallback).toHaveBeenCalledTimes(1);
});

test('controlar valor de retorno', () => {
const mockFunction = jest.fn();
mockFunction.mockReturnValue('valor simulado');

const resultado = mockFunction();
expect(resultado).toBe('valor simulado');
});

Módulos Mock​

¿Cuándo usar? Cuando necesitas reemplazar módulos completos, como librerías de HTTP, bases de datos, o servicios externos.

// Mockear axios para no hacer llamadas HTTP reales
jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;

test('llamada a API mockeada', async () => {
const userData = {id: 1, name: 'Juan'};
mockedAxios.get.mockResolvedValue({data: userData});

const result = await obtenerUsuario(1);

expect(mockedAxios.get).toHaveBeenCalledWith('/api/usuarios/1');
expect(result).toEqual(userData);
});

Setup y Teardown​

¿Para qué sirven? Te permiten preparar el entorno antes de las pruebas y limpiarlo después. Esto es crucial para mantener las pruebas independientes entre sí.

¿Cuándo usar cada uno?

  • beforeAll/afterAll: Configuración costosa que se puede reutilizar (conexión a BD, servidor de prueba)
  • beforeEach/afterEach: Configuración que debe ser fresca para cada prueba (datos de prueba, mocks)
describe('Pruebas de base de datos', () => {
let database;

beforeAll(async () => {
// Se ejecuta una sola vez antes de todas las pruebas
// Útil para configuración costosa
database = await conectarBaseDatos();
await database.ejecutarMigraciones();
});

afterAll(async () => {
// Limpieza final
await database.cerrarConexion();
});

beforeEach(async () => {
// Se ejecuta antes de CADA prueba
// Útil para tener datos frescos en cada prueba
await database.limpiarTablas();
await database.insertarDatosPrueba();
});

afterEach(() => {
// Limpieza después de cada prueba
jest.clearAllMocks();
});

test('debe crear usuario', async () => {
const usuario = await crearUsuario({nombre: 'Juan'});
expect(usuario).toHaveProperty('id');
});

test('debe eliminar usuario', async () => {
const usuario = await crearUsuario({nombre: 'Pedro'});
await eliminarUsuario(usuario.id);

const usuarioEliminado = await buscarUsuario(usuario.id);
expect(usuarioEliminado).toBeNull();
});
});

Configuración Avanzada​

¿Por qué necesitas configuración? Jest funciona bien "out of the box", pero para proyectos reales necesitas personalizar su comportamiento.

jest.config.js​

module.exports = {
// Entorno de ejecución
testEnvironment: 'node', // Para APIs/backend
// testEnvironment: 'jsdom', // Para frontend/DOM

// Cobertura de código
collectCoverage: true,
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'html'],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
},

// Patrones de archivos de prueba
testMatch: [
'**/__tests__/**/*.(js|jsx|ts|tsx)',
'**/*.(test|spec).(js|jsx|ts|tsx)'
],

// Configuración inicial
setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],

// Transformaciones
transform: {
'^.+\\.(js|jsx|ts|tsx)$': 'babel-jest'
}
};

Scripts útiles en package.json​

{
"scripts": {
"test": "jest",
"test:watch": "jest --watch", // Re-ejecuta al cambiar archivos
"test:coverage": "jest --coverage", // Genera reporte de cobertura
"test:ci": "jest --ci --coverage --watchAll=false" // Para CI/CD
}
}

Mejores Prácticas​

1. Nombres Descriptivos​

¿Por qué importa? Los nombres de las pruebas son documentación. Deben explicar qué hace la función y en qué condiciones.

// Mal - No dice nada útil
test('suma', () => {
expect(add(2, 3)).toBe(5);
});

// Bien - Describe comportamiento específico
test('debe sumar correctamente dos números positivos', () => {
expect(add(2, 3)).toBe(5);
});

test('debe manejar números negativos en la suma', () => {
expect(add(-2, 3)).toBe(1);
});

test('debe lanzar error cuando los parámetros no son números', () => {
expect(() => add('a', 3)).toThrow('Los parámetros deben ser números');
});

2. Arrange, Act, Assert (AAA)​

¿Qué es el patrón AAA? Una estructura que hace las pruebas más legibles y mantenibles:

  • Arrange: Preparar los datos y condiciones
  • Act: Ejecutar la función a probar
  • Assert: Verificar el resultado
test('debe calcular descuento correctamente', () => {
// Arrange (Preparar)
const precio = 100;
const porcentajeDescuento = 20;
const descuentoEsperado = 20;

// Act (Actuar)
const descuento = calcularDescuento(precio, porcentajeDescuento);

// Assert (Verificar)
expect(descuento).toBe(descuentoEsperado);
});

3. Casos Edge (Casos Límite)​

¿Por qué son importantes? Los bugs a menudo ocurren en los límites: valores nulos, arrays vacíos, strings vacíos, números muy grandes, etc.

describe('validación de email', () => {
test('debe aceptar email válido', () => {
expect(validarEmail('user@domain.com')).toBe(true);
});

// Casos edge
test('debe rechazar email sin @', () => {
expect(validarEmail('userdomain.com')).toBe(false);
});

test('debe rechazar string vacío', () => {
expect(validarEmail('')).toBe(false);
});

test('debe manejar null y undefined', () => {
expect(validarEmail(null)).toBe(false);
expect(validarEmail(undefined)).toBe(false);
});

test('debe rechazar email con espacios', () => {
expect(validarEmail('user @domain.com')).toBe(false);
});
});

4. Una Sola Responsabilidad por Test​

¿Por qué? Si una prueba falla, debes saber exactamente qué está roto. Si una prueba verifica múltiples cosas, es difícil saber cuál falló.

// Mal - Múltiples responsabilidades
test('operaciones de usuario', () => {
const usuario = crearUsuario('Juan'); // Primera responsabilidad
expect(usuario).toBeDefined();

const actualizado = actualizarUsuario(usuario.id, {edad: 25}); // Segunda
expect(actualizado.edad).toBe(25);

const eliminado = eliminarUsuario(usuario.id); // Tercera
expect(eliminado).toBe(true);
});

// Bien - Una responsabilidad cada una
test('debe crear usuario correctamente', () => {
const usuario = crearUsuario('Juan');
expect(usuario).toBeDefined();
expect(usuario.nombre).toBe('Juan');
});

test('debe actualizar edad de usuario existente', () => {
const usuario = crearUsuario('Juan');
const actualizado = actualizarUsuario(usuario.id, {edad: 25});
expect(actualizado.edad).toBe(25);
});

test('debe eliminar usuario existente', () => {
const usuario = crearUsuario('Juan');
const eliminado = eliminarUsuario(usuario.id);
expect(eliminado).toBe(true);
});

Comandos Útiles​

¿Cuándo usar cada comando?

  • npm test - Ejecuta todas las pruebas una vez
  • npm test -- --watch - Modo desarrollo (re-ejecuta automáticamente)
  • npm test -- --coverage - Ver qué tanto código está cubierto por pruebas
  • npm test -- --verbose - Ver detalles de cada prueba individual
  • npm test -- archivo.test.js - Ejecutar solo un archivo específico

Debugging​

¿Cómo depurar pruebas que fallan?

test('debugging ejemplo', () => {
const input = {nombre: 'Juan', edad: 25};
const resultado = procesarUsuario(input);

// Usar console.log para ver valores
console.log('Input:', input);
console.log('Resultado:', resultado);
console.log('Tipo de resultado:', typeof resultado);

expect(resultado).toHaveProperty('id');
});

// Para debugging más avanzado
test('debugging con breakpoint', () => {
debugger; // Pausa aquí cuando ejecutas con --inspect-brk
const resultado = miFuncion();
expect(resultado).toBeDefined();
});

Recursos adicionales:

Historial de cambios​

Tipo de versiónDescripciónFechaColaborador
1.0Crear guía de Jest.27/05/2025Daniel Queijeiro