¿Qué es Vitest?
Vitest es un test runner impulsado por Vite. Reutiliza tu configuración de Vite actual —alias, plugins, transformaciones de TypeScript y JSX— para que las pruebas se ejecuten a través del mismo pipeline que tu aplicación. Esa base compartida es la razón por la cual inicia rápido y se mantiene rápido.
La API te resultará familiar si has utilizado Jest. describe, it, expect y los helpers de mocking funcionan de la misma manera. Lo que cambia es el motor interno: Vitest ejecuta las pruebas en workers en paralelo, monitorea los archivos con el grafo de módulos de Vite y ofrece soporte nativo para TypeScript sin necesidad de configuración adicional.
Escribiendo tu primera prueba
Un archivo de prueba importa desde vitest y describe el comportamiento que verifica.
// sum.test.ts
import { describe, it, expect } from "vitest";
import { sum } from "./sum";
describe("sum", () => {
it("adds two numbers", () => {
expect(sum(2, 3)).toBe(5);
});
it("handles negatives", () => {
expect(sum(-1, -1)).toBe(-2);
});
});
Nombra las pruebas según el comportamiento, no según la función. Una prueba fallida que diga “suma dos números” te indica qué se rompió; una que diga “prueba de suma 1” no lo hace. Agrupa los casos relacionados con describe y utiliza beforeEach y afterEach para la configuración y limpieza.
Assertions
Vitest incluye una API de matchers muy completa, similar a la de Jest.
// assertions.test.ts
expect(value).toBe(5); // strict equality
expect(value).toEqual({ a: 1 }); // deep equality
expect(value).toBeTruthy();
expect(list).toHaveLength(3);
expect(list).toContain("a");
expect(fn).toThrow("invalid");
expect(value).toBeGreaterThan(3);
Usa toEqual para objetos y arrays, y toBe para primitivos y comprobaciones de referencia. toMatchObject y toContainEqual son útiles cuando solo te interesa una parte de una estructura.
Mocks y spies
El aislamiento es fundamental para las pruebas unitarias. Vitest proporciona vi.fn para mocks, vi.spyOn para envolver métodos existentes y vi.mock para reemplazar módulos completos.
// api.test.ts
import { vi, describe, it, expect } from "vitest";
import { fetchUser } from "./api";
vi.mock("./http", () => ({
get: vi.fn().mockResolvedValue({ id: 1, name: "Ada" }),
}));
describe("fetchUser", () => {
it("returns the user", async () => {
await expect(fetchUser(1)).resolves.toEqual({ id: 1, name: "Ada" });
});
});
vi.mock se eleva (hoisting) por encima de los imports, por lo que el mock se registra antes de que el módulo bajo prueba lo cargue. Cuando la factory necesita una variable definida en el archivo de prueba, utiliza vi.hoisted para que el valor exista antes de que se ejecute el mock.
Pruebas asíncronas
Espera a que la operación finalice y realiza la aserción sobre el resultado. Vitest proporciona los helpers resolves y rejects para promesas.
// async.test.ts
it("rejects on failure", async () => {
await expect(loadUser(-1)).rejects.toThrow("Invalid id");
});
Prioriza este estilo sobre el callback done. Olvidar llamar a done provoca timeouts difíciles de diagnosticar, y las promesas rechazadas no gestionadas pueden filtrarse entre las pruebas. Para los timers falsos, vi.useFakeTimers() y vi.advanceTimersByTime() te permiten probar debounces e intervalos de manera determinista.
Snapshots
Los snapshots capturan un valor y lo comparan en ejecuciones posteriores. Son útiles para salidas extensas y estables, como datos serializados o markup renderizado, pero son fáciles de usar incorrectamente.
// snapshot.test.ts
it("matches the shape", () => {
expect(createConfig({ debug: true })).toMatchSnapshot();
});
Un snapshot que se actualiza sin ser revisado es peor que no tener ningún test. Utilízalos para estructuras de datos donde una comparación completa sea impráctica, y prefiere aserciones explícitas cuando sepas exactamente qué es lo importante. toMatchInlineSnapshot mantiene el valor esperado dentro del archivo del test, lo que hace que las revisiones sean significativas.
Configuración
Vitest lee un bloque test de tu configuración de Vite o de un vitest.config.ts dedicado.
// vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
environment: "jsdom",
globals: true,
coverage: {
provider: "v8",
reporter: ["text", "html"],
thresholds: { lines: 80 },
},
},
});
La opción environment permite seleccionar node, jsdom, happy-dom o un navegador. Para pruebas de componentes, lo habitual es usar jsdom o happy-dom; para lógica pura, mantén el entorno rápido de node. Los umbrales de cobertura (coverage thresholds) convierten un objetivo de cobertura en una regla obligatoria.
Vitest y la pirámide de pruebas
Vitest es la capa de pruebas unitarias y de integración. Combínalo con una librería de pruebas de componentes como Testing Library para el comportamiento de la UI, y con una herramienta de end-to-end como Playwright para los flujos completos de usuario. Juntos cubren la pirámide: muchas pruebas unitarias rápidas, menos pruebas de integración y un puñado de pruebas end-to-end.
Mejores prácticas
- Prueba el comportamiento, no los detalles de la implementación.
- Mantén cada prueba enfocada en un solo comportamiento.
- Usa
beforeEachpara la configuración compartida y limpia los mocks convi.clearAllMocks. - Prefiere
resolvesyrejectssobre los callbacks dedone. - Haz mock en los límites (red, tiempo, aleatoriedad), no de cada llamada interna.
- Usa umbrales de cobertura para evitar regresiones silenciosas.
- Ejecuta en modo watch durante el desarrollo y en CI en cada push.
Errores comunes
- Hacer aserciones sobre el estado interno o métodos privados en lugar del comportamiento observable.
- Actualizar snapshots sin revisar el diff.
- Olvidar limpiar los mocks entre tests, provocando fugas de estado.
- Usar timers reales, lo que vuelve los tests lentos e inestables (flaky).
- Excederse con los mocks hasta que el test solo verifica los propios mocks.
- Probar el mismo comportamiento en cada nivel de la pirámide.
Próximos pasos
Vitest es la forma más rápida de empezar a testear un proyecto de Vite. Compáralo con el clásico Jest, añade Testing Library para los componentes y Playwright para los flujos end-to-end, y apóyate en Vite para mantener la configuración compartida. Después, escribe algunas pruebas para el código que ya tienes y observa cómo hacer un refactor se vuelve un proceso seguro.