🎯 Vitest — Complete Cheatsheet 🎯
Vitest es un framework de testing nativo de Vite diseñado para velocidad extrema, compatibilidad con Jest y soporte de primera clase para ESM, TypeScript y JSX. A diferencia de herramientas tradicionales, Vitest comparte el mismo motor de transformación que Vite, lo que elimina la necesidad de configuraciones duplicadas, habilita HMR durante las pruebas y reduce el tiempo de feedback a milisegundos. Este cheatsheet cubre desde la instalación y API básica hasta mocking avanzado, manipulación de tiempo, entornos aislados, cobertura con V8, modo navegador, workspaces para monorepos y patrones de producción. Ideal para desarrolladores frontend, fullstack y equipos que migran desde Jest o buscan un ciclo de prueba instantáneo sin sacrificar compatibilidad ni ecosistema.
1. 🌟 Conceptos Fundamentales
- Vite-Native: Reutiliza el pipeline de transformación de Vite (
esbuild,rollup, plugins oficiales). No requiere compilación separada para tests.- Por qué importa: Arranque instantáneo, HMR real durante
vitest --watch, cero configuración duplicada.
- Por qué importa: Arranque instantáneo, HMR real durante
- Compatibilidad con Jest: API idéntica (
describe,it,expect,beforeEach,jest.mockalias). Migración drop-in en la mayoría de proyectos.- Diferencia clave: Vitest usa
vien lugar dejestpara mocks/timers, evitando conflictos con módulos globales.
- Diferencia clave: Vitest usa
- ESM-First: Resuelve módulos nativos de Node y navegadores. Soporta
import/exportsin transpilación previa a CommonJS.- Implicación:
__dirname,require, ymodule.exportsno están disponibles por defecto. Usaimport.meta.urlypathonode:url.
- Implicación:
- Watch Mode Inteligente: Solo vuelve a ejecutar archivos modificados o dependientes afectados. Detecta cambios en
src/,tests/y configs en tiempo real. - Entornos Aislados: Ejecuta cada archivo en un contexto limpio por defecto. Previene contaminación de estado entre tests.
- Concurrencia Nativa:
describe.concurrenty--pool-threadspermiten ejecución paralela segura sin bloquear el event loop. - Coverage con V8: Usa
@vitest/coverage-v8para análisis preciso de cobertura basado en instrumentación nativa del motor JS, sin sobrecarga de Babel/Istanbul.
2.
Instalación y Configuración
- Instalación básica:
npm install -D vitest # Opción recomendada: usar con Vite existente npm install -D vitest jsdom @vitest/coverage-v8 - Inicialización automática:
npx vitest init # Genera vitest.config.ts y actualiza package.json scripts package.jsonscripts:{ "scripts": { "test": "vitest", "test:ui": "vitest --ui", "test:coverage": "vitest run --coverage", "test:watch": "vitest --watch", "test:ci": "vitest run --reporter=verbose --coverage" } }vitest.config.tsestándar:import { defineConfig } from 'vitest/config' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], test: { globals: true, // Opcional: evita importar 'describe', 'it', 'expect' environment: 'jsdom', // o 'happy-dom', 'node' (default) setupFiles: './tests/setup.ts', coverage: { provider: 'v8', reporter: ['text', 'json', 'html'], exclude: ['**/*.test.ts', '**/*.spec.ts', 'node_modules/', 'dist/'], }, }, })- Flags CLI esenciales:
Flag Función --watchModo interactivo (default) --runEjecuta una vez y sale (CI) --uiAbre dashboard visual en localhost:51204--coverageGenera reporte de cobertura --reporter=verboseMuestra cada test individualmente --bail=NDetiene tras Nfallos--shard=x/yDivide suite en ypartes, ejecutax(CI paralela)
3. 📝 Sintaxis y Assertions Básicos
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
describe('Math Utilities', () => {
// beforeEach/afterEach por archivo o describe
beforeEach(() => { /* reset state */ })
afterEach(() => { vi.restoreAllMocks() }) // Limpieza automática recomendada
it('sums two positive numbers', () => {
expect(2 + 3).toBe(5)
})
it('throws on invalid input', () => {
expect(() => divide(10, 0)).toThrow('Division by zero')
})
})
// Test sin describe (flat structure)
it('validates email format', () => {
expect(isValidEmail('user@domain.com')).toBe(true)
})
// Test.only / it.skip (filtrado rápido)
it.only('focuses this test during debug', () => { /* ... */ })
it.skip('temporarily disabled', () => { /* ... */ })
4. 🔀 Matchers Avanzados y Modificadores
| Categoría | Matchers Clave | Ejemplo de Uso |
|---|---|---|
| Igualdad | toBe, toEqual, toStrictEqual | expect(obj).toEqual({ a: 1 }) (ignora prototipo) |
| Tipos | toBeNull, toBeUndefined, toBeTruthy, toBeTypeOf | expect(val).toBeTypeOf('string') |
| Números | toBeGreaterThan, toBeCloseTo, toBeNaN | expect(0.1 + 0.2).toBeCloseTo(0.3, 5) |
| Strings | toMatch, toContain, toHaveLength | expect(text).toMatch(/^https?:\/\//) |
| Arrays/Obj | toContainEqual, toHaveProperty, toMatchObject | expect(arr).toContainEqual({ id: 1 }) |
| Async/Promises | resolves, rejects | await expect(fetchData()).resolves.toMatchObject({ status: 200 }) |
- Modificadores de negación:
expect(val).not.toBe(null) - Asertos personalizados:
import { expect } from 'vitest' expect.extend({ toBeWithinRange(received, floor, ceiling) { const pass = received >= floor && received <= ceiling return { pass, message: () => `expected ${received} ${pass ? 'not ' : ''}to be within ${floor} - ${ceiling}`, } }, }) expect(5).toBeWithinRange(1, 10) // ✅
5. 🎭 Mocking, Spies y Simulación
import { vi, describe, it, expect } from 'vitest'
import { fetchUser } from './api'
import * as db from './database'
// Mock completo de módulo (debe ir ANTES de importaciones si se usa vi.mock)
vi.mock('./database', () => ({
default: { query: vi.fn() }
}))
describe('API Layer', () => {
it('returns mocked user', async () => {
db.query.mockResolvedValue({ id: 1, name: 'Alice' })
const user = await fetchUser(1)
expect(user.name).toBe('Alice')
expect(db.query).toHaveBeenCalledWith('SELECT * FROM users WHERE id = ?', [1])
})
it('tracks spy calls', () => {
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
console.log('hidden')
expect(consoleSpy).toHaveBeenCalledTimes(1)
expect(consoleSpy).toHaveBeenCalledWith('hidden')
})
it('auto-restore after test', () => {
const timerSpy = vi.spyOn(global, 'setTimeout').mockReturnValue(123)
// Vitest restaura automáticamente si usas vi.restoreAllMocks() en afterEach
})
})
- Hoisting de mocks:
vi.mock()yvi.hoisted()se elevan al top del archivo durante transformación. Úsalos para simular módulos antes de cargar dependencias. - Fábricas dinámicas:
vi.mock('./utils', async (importOriginal) => { const actual = await importOriginal<typeof import('./utils')>() return { ...actual, formatDate: vi.fn(() => '2024-01-01'), } })
6. ⏱️ Testing Asíncrono y Manipulación del Tiempo
import { vi, it, expect, describe } from 'vitest'
// Control de temporizadores (setTimeout, setInterval, Date)
vi.useFakeTimers()
describe('Time-sensitive logic', () => {
it('schedules callback correctly', () => {
const callback = vi.fn()
setTimeout(callback, 1000)
expect(callback).not.toHaveBeenCalled()
vi.advanceTimersByTime(999)
expect(callback).not.toHaveBeenCalled()
vi.advanceTimersByTime(1) // Total 1000ms
expect(callback).toHaveBeenCalledTimes(1)
})
it('mocks Date.now()', () => {
const fixedDate = new Date('2024-06-01T00:00:00Z')
vi.setSystemTime(fixedDate)
expect(new Date().toISOString()).toBe('2024-06-01T00:00:00.000Z')
})
})
// Async sin fake timers
it('polls until condition met', async () => {
vi.useFakeTimers()
const poll = vi.fn(() => Promise.resolve({ ready: false }))
poll.mockResolvedValueOnce({ ready: true })
const promise = waitForReady(poll)
vi.advanceTimersByTime(500)
await expect(promise).resolves.toEqual({ ready: true })
})
- Restauración:
vi.useRealTimers()revierte al comportamiento nativo. Siempre restaura enafterEach. - Promesas y microtasks:
vi.advanceTimersByTimeAsync()esperaPromiseyqueueMicrotask. Vital para Reactact()o frameworks async.
7. 🌍 Entornos y Estado Global
| Entorno | Uso típico | Instalación |
|---|---|---|
node | Lógica pura, CLI, APIs | Built-in |
jsdom | DOM completo, compatibilidad legacy | npm i -D jsdom |
happy-dom | DOM rápido, modern APIs | npm i -D happy-dom |
edge-runtime | Deno, Cloudflare Workers | npm i -D @edge-runtime/vm |
// vitest.config.ts
export default defineConfig({
test: {
environment: 'happy-dom',
globals: true,
setupFiles: ['./tests/setup.ts'],
globalSetup: ['./tests/globalSetup.ts'], // Ejecuta antes/después de TODOS los tests
},
})
setupFiles: Se ejecuta antes de cada archivo de prueba. Ideal para mock globales,expect.extend, polyfills.globalSetup: Función async que retornateardown. Perfecto para iniciar DBs locales, servidores mock o limpiar caches.// globalSetup.ts export default async function globalSetup() { const server = await startMockServer() return async () => { await server.close() } }- Variables de entorno por test:
import.meta.env.VITE_API_URLse inyecta desdevitest.config.tso.env.test.
8. 📊 Cobertura, Snapshots y Reportes
- Cobertura V8:
npm run test:coverage # Genera coverage/index.html, coverage/coverage-final.jsoncoverage: { thresholds: { lines: 80, branches: 75, functions: 90, statements: 85, }, watermarks: { lines: [80, 95], functions: [70, 90], }, } - Snapshots:
it('renders user card correctly', () => { const html = renderCard({ name: 'Ana', role: 'admin' }) expect(html).toMatchSnapshot() })- Actualización:
vitest -uonpx vitest --update - Snapshots inline:
expect(html).toMatchInlineSnapshot()
- Actualización:
- Reportes visuales:
--reporter=defaulto--reporter=jsonpara integración con CI/CD (GitHub Actions, GitLab, Jenkins). - Filtrado por cobertura:
coverage.includeycoverage.excludeusan patrones glob. Excluye tests, configs y assets estáticos.
9.
Workspaces, Browser Mode y UI
- Workspaces (Monorepos):
// vitest.workspace.ts export default [ 'packages/ui/vitest.config.ts', 'packages/api/vitest.config.ts', { test: { include: ['tests/e2e/**/*.spec.ts'] } }, ]- Ejecuta
vitesten raíz. Orquesta suites paralelas con reporting unificado.
- Ejecuta
- Browser Mode (Experimental/Nativo):
test: { browser: { enabled: true, name: 'chrome', // o 'firefox', 'webkit' headless: true, provider: 'playwright', // o 'webdriverio' }, }- Ejecuta tests reales en navegadores. Ideal para componentes UI, Web APIs y compatibilidad cross-browser.
- UI Dashboard:
vitest --uiabre interfaz web para filtrar tests, ver logs, ejecutar individualmente y navegar errores con stack traces enriquecidos. - Test Context: Pasa datos entre
beforeEach,ityafterEachsin variables globales.it('uses test context', ({ expect }) => { expect(1 + 1).toBe(2) }) // O con fixtures custom export const test = vitest.extend<{ db: MockDB }>({ db: async ({}, use) => { const db = new MockDB() await use(db) await db.cleanup() }, })
10. ⚠️ Errores Comunes y Trampas
- Mocks no restaurados entre tests:
vi.fn()mantiene contadores entreit. Causa falsos positivos.- Fix:
afterEach(() => { vi.restoreAllMocks(); vi.clearAllMocks() })en setup global.
- Fix:
vi.mock()después de imports: El mock se aplica después de cargar el módulo real. El test usa la implementación original.- Fix: Coloca
vi.mock()en la primera línea del archivo o usavi.hoisted()para lógica dinámica.
- Fix: Coloca
- Confundir
toBevstoEqual:toBeusa===(referencia).toEqualcompara estructura recursiva.- Fix: Usa
toStrictEqualpara igualdad exacta (incluye propiedadesundefinedvs ausentes).
- Fix: Usa
- Async sin
awaiten assertions:expect(promise).resolves.toBe(5)falla si no seawaito no se maneja la promesa.- Fix:
await expect(fn()).resolves.toBe(5)oexpect(fn()).resolves.toBe(5)sinawaitsi solo verificas la promesa.
- Fix:
- Cobertura inflada por mocks: Instrumentación V8 cuenta líneas de mocks como ejecutadas.
- Fix: Excluye
__mocks__/y archivos de setup encoverage.exclude. Usacoverage.ignoreEmptyLines: true.
- Fix: Excluye
- Snapshots frágiles en UI: Renderizado con fechas, IDs aleatorios o clases generadas cambia en cada ejecución.
- Fix: Usa
expect.addSnapshotSerializer()para normalizar, o mocks consistentes para datos dinámicos.
- Fix: Usa
- Entorno incorrecto para DOM: Ejecutar tests de componentes en
nodecausaReferenceError: window is not defined.- Fix: Configura
environment: 'jsdom'o'happy-dom'envitest.config.tso usa// @vitest-environment jsdompor archivo.
- Fix: Configura
11.
Mejores Prácticas y Consejos de Experto
- Aísla mocks por test: Usa
vi.fn()dentro deito restaura enafterEach. Evitavi.mock()a nivel global salvo para módulos infraestructurales (router, store). - Prefiere
happy-domsobrejsdom: Más rápido, API moderna, mejor soporte parafetchyMutationObserver. Reduce tiempo de CI en ~30-40%. - Usa
@testing-librarycon Vitest: Combinascreen,userEventyrenderpara tests centrados en usuario, no en implementación.import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' // Vitest maneja el bundling, @testing-library maneja la interacción - Configura
coverage.thresholdsen CI: Falla el pipeline si la cobertura cae debajo del umbral. Manténlines >= 80%,functions >= 85%. - Evita
globals: trueen librerías públicas: Importa explícitamenteimport { describe, it, expect } from 'vitest'para evitar conflictos con Jest u otros runners. - Perfila tests lentos:
vitest --reporter=verbose --bail=1identifica cuellos. Usavi.setConfig({ testTimeout: 5000 })localmente, pero mantén5000en CI. - Snapshots inline para lógica pequeña:
toMatchInlineSnapshot()mantiene snapshots junto al código, facilita review en PRs y reduce drift. - Workspaces para monorepos: Define
vitest.workspace.tsen raíz. Permite ejecutarvitestuna vez y obtener reporte consolidado sin configurar cada paquete. - Browser mode solo para integración real: No uses
browser.enabled: truepara unit tests. Úsalo para validar CSS, Web APIs o compatibilidad cross-browser antes de release. - Documenta
setupFilesyglobalSetup: Centraliza mocks de red, polyfills y limpieza en archivos nombrados. Facilita onboarding y debugging en CI.
Este cheatsheet proporciona una referencia completa para Vitest, cubriendo arquitectura Vite-native, API de assertions, mocking avanzado, manipulación de tiempo, entornos aislados, cobertura V8, workspaces para monorepos, modo navegador y patrones de producción, junto con las mejores prácticas para mantener suites rápidas, deterministas y escalables en entornos reales.