Vitest en 2026: El Nuevo Estándar de Pruebas en JavaScript Moderno
Hola HaWkers, si todavía estás usando Jest en nuevos proyectos, probablemente estás perdiendo tiempo y rendimiento. Vitest se ha consolidado como el framework de pruebas estándar del ecosistema JavaScript moderno en 2026.
Vamos a entender por qué ocurrió el cambio y cómo puedes beneficiarte.
Por Qué Vitest Ganó
El Contexto del Cambio
// Timeline del cambio de Jest a Vitest
const testingTimeline = {
2020: 'Jest es el estándar indiscutible',
2022: 'Vitest 0.x surge con Vite',
2023: 'Vitest 1.0 - listo para producción',
2024: {
vitest: 'Adopción masiva en nuevos proyectos',
jest: 'Todavía dominante en legacy'
},
2025: {
vitest4: 'Vitest 4.0 estable',
angular: 'Angular 21 adopta Vitest como estándar',
breaking: 'Punto de inflexión'
},
2026: {
status: 'Vitest es el nuevo estándar',
jest: 'Mantenido para legacy',
newProjects: '80%+ usan Vitest'
}
};Ventajas de Vitest
// Por qué Vitest superó a Jest
const vitestAdvantages = {
// 1. Rendimiento
performance: {
speedup: '2-10x más rápido que Jest',
why: 'Reutiliza el pipeline de Vite',
watch: 'Watch mode instantáneo',
benchmark: `
Proyecto mediano (500 tests):
Jest: 45 segundos
Vitest: 8 segundos
`
},
// 2. Configuración cero con Vite
zeroConfig: {
if: 'Usas Vite',
then: 'Vitest funciona sin config',
shared: 'Misma config de plugins, aliases, etc',
dx: 'No necesitas mantener dos configs'
},
// 3. ESM nativo
esm: {
jest: 'ESM es un add-on, frecuentemente falla',
vitest: 'ESM es nativo, funciona perfectamente',
impact: 'Puedes testear código ESM-only sin hacks'
},
// 4. TypeScript nativo
typescript: {
jest: 'Requiere ts-jest o babel',
vitest: 'TypeScript funciona out of the box',
types: 'Types incluidos, sin @types/vitest'
},
// 5. API compatible con Jest
compatibility: {
api: 'describe, it, expect - misma API',
migration: 'La mayoría de los tests funcionan sin cambios',
mocks: 'vi.mock() similar a jest.mock()'
},
// 6. Features modernas
modernFeatures: {
browserMode: 'Testea en browser real (Playwright)',
snapshotInline: 'Snapshots inline en el código',
coverage: 'v8 coverage nativo',
ui: 'UI interactiva built-in'
}
};
Comenzando con Vitest
Setup Básico
// Instalación y configuración
// 1. Instalar
// npm install -D vitest
// 2. Agregar script en package.json
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"test:coverage": "vitest run --coverage"
}
}
// 3. Configuración (opcional - funciona sin config con Vite)
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
// Globals (describe, it, expect) sin import
globals: true,
// Ambiente de prueba
environment: 'jsdom', // o 'node', 'happy-dom'
// Setup files
setupFiles: ['./tests/setup.ts'],
// Coverage
coverage: {
provider: 'v8',
reporter: ['text', 'html'],
exclude: ['node_modules/', 'tests/']
},
// Incluir/excluir archivos
include: ['**/*.{test,spec}.{ts,tsx}'],
},
});Escribiendo Tests
// Sintaxis básica - muy similar a Jest
// math.test.ts
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { sum, multiply } from './math';
describe('Math functions', () => {
it('should sum two numbers', () => {
expect(sum(1, 2)).toBe(3);
});
it('should multiply two numbers', () => {
expect(multiply(3, 4)).toBe(12);
});
});
// Con globals: true en config, puedes omitir imports
describe('Math functions', () => {
it('should sum two numbers', () => {
expect(sum(1, 2)).toBe(3);
});
});Mocking
// Mocking en Vitest
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { fetchUser } from './api';
import { UserService } from './UserService';
// Mock de módulo entero
vi.mock('./api', () => ({
fetchUser: vi.fn()
}));
describe('UserService', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('should fetch user by id', async () => {
// Configurar mock
vi.mocked(fetchUser).mockResolvedValue({
id: 1,
name: 'John'
});
const service = new UserService();
const user = await service.getUser(1);
expect(fetchUser).toHaveBeenCalledWith(1);
expect(user.name).toBe('John');
});
});
// Mock de función específica
const mockFn = vi.fn();
mockFn.mockReturnValue(42);
mockFn.mockResolvedValue(asyncValue);
mockFn.mockImplementation((x) => x * 2);
// Spy en método existente
const spy = vi.spyOn(object, 'method');
spy.mockReturnValue('mocked');
// Mock de timers
vi.useFakeTimers();
vi.advanceTimersByTime(1000);
vi.runAllTimers();
vi.useRealTimers();
Testeando Componentes
// Testeando componentes React con Vitest + Testing Library
// Button.test.tsx
import { describe, it, expect } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';
describe('Button', () => {
it('should render with text', () => {
render(<Button>Click me</Button>);
expect(screen.getByRole('button')).toHaveTextContent('Click me');
});
it('should call onClick when clicked', async () => {
const handleClick = vi.fn();
render(<Button onClick={handleClick}>Click</Button>);
await fireEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledOnce();
});
it('should be disabled when loading', () => {
render(<Button loading>Submit</Button>);
expect(screen.getByRole('button')).toBeDisabled();
});
});
// Setup para Testing Library (tests/setup.ts)
import '@testing-library/jest-dom/vitest';Testeando Vue
// Testeando componentes Vue con Vitest
// Counter.test.ts
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import Counter from './Counter.vue';
describe('Counter', () => {
it('should render initial count', () => {
const wrapper = mount(Counter, {
props: { initialCount: 5 }
});
expect(wrapper.text()).toContain('5');
});
it('should increment when button clicked', async () => {
const wrapper = mount(Counter);
await wrapper.find('button').trigger('click');
expect(wrapper.text()).toContain('1');
});
});Features Avanzadas
Browser Mode
// Testear en browser real con Playwright
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
browser: {
enabled: true,
name: 'chromium', // o 'firefox', 'webkit'
provider: 'playwright',
},
},
});
// ¡Los tests corren en browser real!
// Útil para:
// - Canvas, WebGL
// - Web APIs específicas
// - Visual regression
// visual.test.ts
import { describe, it, expect } from 'vitest';
import { page } from '@vitest/browser/context';
describe('Visual tests', () => {
it('should render chart correctly', async () => {
await page.goto('/chart');
// Screenshot para visual regression
await expect(page.locator('.chart')).toMatchScreenshot();
});
});
Inline Snapshots
// Snapshots inline - más conveniente que archivos
import { describe, it, expect } from 'vitest';
import { formatUser } from './formatUser';
describe('formatUser', () => {
it('should format user object', () => {
const user = { name: 'John', age: 30 };
// Snapshot inline - ¡se actualiza en el propio archivo!
expect(formatUser(user)).toMatchInlineSnapshot(`
{
"displayName": "John (30 years old)",
"initials": "J"
}
`);
});
});
// Cuando corres vitest -u, el snapshot se actualiza
// directamente en el archivo de testType Testing
// Testear tipos TypeScript
// types.test-d.ts (nota el -d en el nombre)
import { describe, it, expectTypeOf } from 'vitest';
import { createStore } from './store';
describe('Store types', () => {
it('should infer state type correctly', () => {
const store = createStore({ count: 0 });
// Testea si el tipo está correcto
expectTypeOf(store.state.count).toBeNumber();
expectTypeOf(store.state.count).not.toBeString();
});
it('should accept correct action types', () => {
type Action = { type: 'increment' } | { type: 'decrement' };
expectTypeOf<Action>().toMatchTypeOf<{ type: string }>();
});
});
// vitest.config.ts
export default defineConfig({
test: {
typecheck: {
enabled: true,
},
},
});UI Mode
// Interfaz visual para tests
// Ejecuta: npx vitest --ui
// Features de la UI:
// - Visualizar todos los tests
// - Filtrar por status (passed, failed, skipped)
// - Ver código del test
// - Ver output de consola
// - Re-run tests específicos
// - Module graph visualization
// Excelente para:
// - Debug de tests fallando
// - Entender cobertura
// - Onboarding de nuevos devs
Migrando de Jest a Vitest
Guía de Migración
// Pasos para migrar
const migrationGuide = {
// 1. Instalar Vitest
step1: `
npm remove jest @types/jest ts-jest babel-jest
npm install -D vitest
`,
// 2. Crear config
step2: `
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true, // Para compatibilidad con Jest
environment: 'jsdom',
},
});
`,
// 3. Actualizar scripts
step3: `
// package.json
{
"scripts": {
"test": "vitest",
"test:run": "vitest run"
}
}
`,
// 4. Search and replace
step4: {
from: "jest.fn()",
to: "vi.fn()",
from2: "jest.mock(",
to2: "vi.mock(",
from3: "jest.spyOn(",
to3: "vi.spyOn(",
from4: "@jest/globals",
to4: "vitest"
},
// 5. Actualizar imports (si no usas globals)
step5: `
// Antes
import { jest } from '@jest/globals';
// Después
import { vi } from 'vitest';
`
};Diferencias de API
// Diferencias entre Jest y Vitest
const apiDifferences = {
// Mocking
mocking: {
jest: 'jest.fn(), jest.mock(), jest.spyOn()',
vitest: 'vi.fn(), vi.mock(), vi.spyOn()',
note: 'Solo prefijo diferente'
},
// Timers
timers: {
jest: 'jest.useFakeTimers()',
vitest: 'vi.useFakeTimers()',
extra: 'Vitest tiene vi.advanceTimersByTimeAsync()'
},
// Module mocking
moduleMock: {
jest: `
jest.mock('./module', () => ({
foo: jest.fn()
}));
`,
vitest: `
vi.mock('./module', () => ({
foo: vi.fn()
}));
`,
vitestAlternative: `
// Vitest también soporta:
vi.mock('./module', async (importOriginal) => {
const actual = await importOriginal();
return {
...actual,
foo: vi.fn()
};
});
`
},
// Snapshot
snapshot: {
jest: 'expect(x).toMatchSnapshot()',
vitest: 'expect(x).toMatchSnapshot()',
extra: 'Vitest tiene toMatchInlineSnapshot()'
},
// Globals
globals: {
jest: 'Globals por defecto',
vitest: 'Necesita globals: true o imports explícitos',
recommendation: 'Usa imports explícitos para type safety'
}
};Codemod Automático
// Usa codemod para migración automatizada
// Instalar
// npx @vitest/codemod jest
// El codemod hace:
// - Renombra jest.* a vi.*
// - Actualiza imports
// - Ajusta configuración
// Después, revisa manualmente:
// - Mocks complejos
// - Setup files
// - Custom matchers
Buenas Prácticas con Vitest
Organización de Tests
// Estructura recomendada
const testStructure = {
// Opción 1: Co-located (recomendado)
colocated: `
src/
components/
Button/
Button.tsx
Button.test.tsx ← Junto al código
Button.stories.tsx
utils/
format.ts
format.test.ts ← Junto al código
`,
// Opción 2: Carpeta separada
separate: `
src/
components/
Button.tsx
tests/
components/
Button.test.tsx ← Carpeta separada
`,
recommendation: `
Co-located es mejor:
- Fácil encontrar tests
- Refactorizar junto
- Incentiva escribir tests
`
};Patterns Útiles
// Patterns que funcionan bien
// 1. Factory functions para fixtures
function createUser(overrides = {}) {
return {
id: 1,
name: 'John',
email: 'john@example.com',
...overrides
};
}
it('should update user name', () => {
const user = createUser({ name: 'Jane' });
// ...
});
// 2. Custom matchers
expect.extend({
toBeValidEmail(received) {
const pass = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(received);
return {
pass,
message: () => `expected ${received} to be valid email`
};
}
});
// 3. Testing utilities
function renderWithProviders(ui, options = {}) {
return render(ui, {
wrapper: ({ children }) => (
<QueryClientProvider client={queryClient}>
<ThemeProvider>
{children}
</ThemeProvider>
</QueryClientProvider>
),
...options
});
}
// 4. Describe blocks organizados
describe('UserService', () => {
describe('getUser', () => {
it('should return user when found', () => {});
it('should throw when not found', () => {});
});
describe('createUser', () => {
it('should create user with valid data', () => {});
it('should reject invalid email', () => {});
});
});
Conclusión
Vitest se convirtió en el estándar de pruebas en JavaScript por buenas razones: es más rápido, más moderno, y tiene mejor DX que Jest. La migración es simple y los beneficios son inmediatos.
Cuándo usar Vitest:
- Nuevos proyectos (siempre)
- Proyectos con Vite (migración fácil)
- Proyectos que necesitan ESM
- Cuando el rendimiento de pruebas importa
Cuándo mantener Jest:
- Proyectos legacy funcionando bien
- Costo de migración no compensa
- Dependencias que requieren Jest
Acciones recomendadas:
- Usa Vitest en nuevos proyectos
- Planea migración de proyectos activos
- Explora browser mode para pruebas visuales
- Usa UI mode para debug
Para entender más sobre el ecosistema JavaScript moderno, lee: VoidZero 2026.

