¿Qué es Jest?
Jest es un test runner de JavaScript creado en Facebook y actualmente una de las herramientas de testing más utilizadas en el ecosistema. Es una solución batteries-included: las aserciones, el mocking, el snapshot testing y la cobertura vienen incluidos en un solo paquete, por lo que un proyecto nuevo requiere muy poca configuración para comenzar.
Durante años, Jest fue la opción predeterminada para proyectos de React y Node.js, y sigue siendo dominante en las bases de código existentes. Incluso con el ascenso de Vitest, la API de Jest es el vocabulario compartido del testing en JavaScript, lo que hace que valga la pena conocerlo independientemente del runner que utilices en tu día a día.
Escribiendo tests
Un archivo de test de Jest utiliza describe para agrupar y test (o it) para declarar un caso.
// cart.test.js
import { Cart } from "./cart";
describe("Cart", () => {
test("starts empty", () => {
expect(new Cart().items).toEqual([]);
});
test("adds an item", () => {
const cart = new Cart();
cart.add({ id: 1, price: 10 });
expect(cart.items).toHaveLength(1);
});
});
Usa beforeEach y afterEach para el setup y teardown, y beforeAll y afterAll cuando un recurso deba crearse una sola vez para todo el archivo. Nombra los tests basándote en el comportamiento, para que un fallo te indique exactamente qué se rompió.
Matchers
La API de expect de Jest es amplia y expresiva.
// matchers.test.js
expect(value).toBe(5); // strict equality
expect(value).toEqual({ a: 1 }); // deep equality
expect(value).toBeDefined();
expect(list).toContain("a");
expect(fn).toThrow("invalid");
expect(value).toBeGreaterThan(3);
expect(obj).toMatchObject({ id: 1 }); // partial match
toBe compara con Object.is y es la opción adecuada para primitivos. toEqual compara objetos y arrays de forma recursiva. toStrictEqual es más estricto con los tipos y las propiedades undefined. toMatchObject es útil cuando solo te interesa un subconjunto de una estructura.
Mocks, spies y mocking de módulos
El sistema de mocking de Jest es una de sus características más potentes.
// users.test.js
import { jest } from "@jest/globals";
import { getUser } from "./users";
jest.mock("./http", () => ({
get: jest.fn().mockResolvedValue({ id: 1, name: "Ada" }),
}));
test("returns a user", async () => {
await expect(getUser(1)).resolves.toEqual({ id: 1, name: "Ada" });
});
jest.fn crea una función mock, jest.spyOn envuelve un método real para que puedas observar las llamadas y restaurarlo más tarde, y jest.mock reemplaza un módulo. Debido a que jest.mock se eleva (hoisting) por encima de los imports, el mock ya está configurado antes de que se cargue el módulo bajo prueba.
Limpia siempre los mocks entre tests. Activa clearMocks, resetMocks o restoreMocks en la configuración, o llama al helper ...AllMocks correspondiente en afterEach. El estado residual de los mocks es una de las razones más comunes por las que los tests pasan individualmente pero fallan cuando se ejecutan juntos.
Pruebas asíncronas
Usa async/await con resolves y rejects, exactamente igual que lo harías en Vitest.
// async.test.js
test("rejects on failure", async () => {
await expect(loadUser(-1)).rejects.toThrow("Invalid id");
});
El callback done funciona, pero es fácil usarlo incorrectamente. Es preferible usar await. Para los temporizadores, jest.useFakeTimers() junto con jest.advanceTimersByTime() hace que las pruebas de debounce y reintentos sean deterministas.
Snapshots
Los snapshots serializan un valor y lo comparan en ejecuciones futuras.
// config.test.js
test("builds the default config", () => {
expect(createConfig({ debug: true })).toMatchInlineSnapshot();
});
Los inline snapshots residen en el archivo de prueba, lo que permite revisarlos en un diff. Los snapshots externos son convenientes para salidas de datos extensas, pero frecuentemente se actualizan sin inspección, lo que los convierte en un simple trámite. Utiliza snapshots para datos serializables estables, no como un sustituto de analizar qué es lo que realmente importa.
Configuración
Jest lee la configuración desde jest.config.js, una clave jest en package.json o un flag de la CLI.
// jest.config.js
export default {
testEnvironment: "jsdom",
clearMocks: true,
collectCoverage: true,
coverageThreshold: {
global: { lines: 80, functions: 80 },
},
transform: {
"^.+\\.(t|j)sx?$": ["@swc/jest"],
},
};
testEnvironment selecciona node o jsdom. clearMocks evita fugas de memoria (leakage). Los umbrales de cobertura (coverage thresholds) convierten un objetivo en una regla obligatoria. El transform decide cómo se compilan TypeScript y JSX, siendo ts-jest, Babel y SWC las opciones más comunes.
Dónde encaja Jest
Jest es la capa de pruebas unitarias y de integración. Para el comportamiento de los componentes, combínalo con Testing Library para realizar pruebas desde la perspectiva del usuario. Para flujos de usuario completos, utiliza una herramienta end-to-end como Playwright o Cypress. Si estás iniciando un nuevo proyecto con Vite, Vitest ofrece la misma API con un pipeline compartido más rápido.
Mejores prácticas
- Prueba el comportamiento a través de APIs públicas, no de implementaciones internas privadas.
- Mantén una sola aserción lógica por prueba siempre que sea práctico.
- Reinicia y restaura los mocks entre pruebas.
- Prefiere
async/awaitsobredone. - Usa fake timers para cualquier funcionalidad que dependa del tiempo.
- Crea snapshots de estructuras pequeñas y estables, y revisa cada actualización.
- Ejecuta las pruebas en CI con cada cambio, no solo de forma local.
Errores comunes
- Hacer aserciones sobre detalles de implementación, lo que provoca que los tests fallen durante los refactors.
- Actualizar snapshots a ciegas.
- Olvidar limpiar los mocks, provocando fugas de estado entre tests.
- Usar timers reales, haciendo que los tests sean lentos o inestables (flaky).
- Probar todo a nivel unitario y pasar por alto errores de integración.
- Ignorar la cobertura asumiendo que los tests cubren los flujos más importantes.
Próximos pasos
Jest es una base confiable y un vocabulario compartido en todo el ecosistema. Compáralo con Vitest para proyectos modernos de Vite, añade Testing Library para los componentes y cubre los flujos de usuario con Playwright o Cypress. Después, escribe una prueba para un bug que hayas corregido recientemente y deja que te proteja contra regresiones.