Por qué Vitest para proyectos Vue
Vitest es el test runner recomendado por el equipo de Vue para proyectos basados en Vite. Al compartir la misma configuración y pipeline de transformación que Vite, Vitest elimina la duplicación de configuraciones y ofrece una experiencia de desarrollo fluida.
Las principales ventajas de Vitest frente a otras alternativas son:
- Nativo de Vite: reutiliza la configuración de
vite.config.ts, incluyendo plugins, alias y transformaciones - API compatible con Jest: las funciones
describe,it,expecty los mocks funcionan de la misma forma - Rendimiento superior: ejecución en paralelo con worker threads y transformación bajo demanda
- Soporte nativo de ESM y TypeScript: sin necesidad de configuración adicional
- Modo watch inteligente: solo re-ejecuta los tests afectados por los cambios
Instalación y configuración
Para instalar Vitest en un proyecto Vue existente:
npm install -D vitest
Vitest puede usar directamente el archivo vite.config.ts existente. Para definir opciones específicas de testing, se utiliza la propiedad test:
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
test: {
globals: true,
environment: 'jsdom',
},
})
La opción globals: true permite usar describe, it y expect sin importarlos. La opción environment: 'jsdom' simula un entorno de navegador para tests que interactúan con el DOM.
Si se prefiere separar la configuración, se puede crear un archivo vitest.config.ts:
// vitest.config.ts
import { defineConfig, mergeConfig } from 'vitest/config'
import viteConfig from './vite.config'
export default mergeConfig(viteConfig, defineConfig({
test: {
globals: true,
environment: 'jsdom',
},
}))
Para que TypeScript reconozca los globales de Vitest, se añade al tsconfig.app.json:
{
"compilerOptions": {
"types": ["vitest/globals"]
}
}
Convenciones de archivos de test
Vitest detecta automáticamente archivos que coincidan con estos patrones:
**/*.test.tso**/*.test.js**/*.spec.tso**/*.spec.js
Una organización común es colocar los tests junto al código fuente:
src/
utils/
formatters.ts
formatters.test.ts
services/
api.ts
api.test.ts
Estructura de un test: describe, it y expect
La estructura básica de un archivo de test agrupa los tests relacionados con describe y define cada caso con it o test:
import { describe, it, expect } from 'vitest'
import { calcularDescuento } from './pricing'
describe('calcularDescuento', () => {
it('aplica un 10% de descuento para compras superiores a 100', () => {
const resultado = calcularDescuento(200, 10)
expect(resultado).toBe(180)
})
it('no aplica descuento si el porcentaje es 0', () => {
const resultado = calcularDescuento(200, 0)
expect(resultado).toBe(200)
})
it('lanza error si el porcentaje es negativo', () => {
expect(() => calcularDescuento(200, -5)).toThrow('Porcentaje inválido')
})
})
Matchers comunes
Vitest proporciona un conjunto de matchers para realizar aserciones:
// Igualdad
expect(2 + 2).toBe(4) // igualdad estricta (===)
expect({ a: 1 }).toEqual({ a: 1 }) // igualdad profunda de objetos
// Veracidad
expect(true).toBeTruthy()
expect(0).toBeFalsy()
expect(null).toBeNull()
expect(undefined).toBeUndefined()
expect('hello').toBeDefined()
// Números
expect(10).toBeGreaterThan(5)
expect(0.1 + 0.2).toBeCloseTo(0.3)
// Strings y arrays
expect('equipo').toContain('equi')
expect([1, 2, 3]).toContain(2)
expect([{ id: 1 }, { id: 2 }]).toContainEqual({ id: 1 })
// Excepciones
expect(() => { throw new Error('fallo') }).toThrow('fallo')
// Funciones mock
const fn = vi.fn()
fn('arg1')
expect(fn).toHaveBeenCalled()
expect(fn).toHaveBeenCalledWith('arg1')
expect(fn).toHaveBeenCalledTimes(1)
Setup y teardown
Para ejecutar lógica antes y después de los tests se utilizan las funciones de setup y teardown:
import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll } from 'vitest'
describe('UserService', () => {
let servicio: UserService
beforeAll(() => {
// Se ejecuta una vez antes de todos los tests del describe
console.log('Iniciando suite de tests')
})
afterAll(() => {
// Se ejecuta una vez después de todos los tests del describe
console.log('Suite completada')
})
beforeEach(() => {
// Se ejecuta antes de cada test individual
servicio = new UserService()
})
afterEach(() => {
// Se ejecuta después de cada test individual
servicio.reset()
})
it('devuelve una lista de usuarios', () => {
const usuarios = servicio.getAll()
expect(usuarios).toEqual([])
})
})
Mocking con vi
Vitest proporciona el objeto vi con utilidades para crear mocks, espías y reemplazar módulos:
vi.fn() - Funciones mock
import { describe, it, expect, vi } from 'vitest'
const callback = vi.fn()
callback('dato1')
callback('dato2')
expect(callback).toHaveBeenCalledTimes(2)
expect(callback).toHaveBeenCalledWith('dato1')
// Mock con implementacion
const sumar = vi.fn((a: number, b: number) => a + b)
expect(sumar(2, 3)).toBe(5)
vi.spyOn() - Espiar métodos existentes
const carrito = {
items: [] as string[],
agregar(item: string) {
this.items.push(item)
}
}
const spy = vi.spyOn(carrito, 'agregar')
carrito.agregar('producto1')
expect(spy).toHaveBeenCalledWith('producto1')
expect(carrito.items).toContain('producto1')
spy.mockRestore()
vi.mock() - Reemplazar módulos completos
import { describe, it, expect, vi } from 'vitest'
import { fetchUsers } from './api'
vi.mock('./api', () => ({
fetchUsers: vi.fn(() => Promise.resolve([
{ id: 1, name: 'Ana' },
{ id: 2, name: 'Carlos' }
]))
}))
describe('fetchUsers mock', () => {
it('devuelve usuarios mockeados', async () => {
const users = await fetchUsers()
expect(users).toHaveLength(2)
expect(users[0].name).toBe('Ana')
})
})
Testing de código asíncrono
Los tests asíncronos se escriben con async/await:
import { describe, it, expect } from 'vitest'
async function obtenerDatos(id: number): Promise<{ id: number; nombre: string }> {
const response = await fetch(`/api/items/${id}`)
return response.json()
}
describe('obtenerDatos', () => {
it('devuelve los datos del item', async () => {
// Asumiendo que fetch está mockeado
const datos = await obtenerDatos(1)
expect(datos).toHaveProperty('id')
expect(datos).toHaveProperty('nombre')
})
it('lanza error con id inválido', async () => {
await expect(obtenerDatos(-1)).rejects.toThrow()
})
})
Ejecución de tests
Vitest se ejecuta desde la terminal con diferentes modos:
# Modo watch (por defecto): re-ejecuta al detectar cambios
npx vitest
# Ejecución única sin watch
npx vitest run
# Ejecutar tests que coincidan con un patrón
npx vitest run formatters
# Modo watch explícito
npx vitest watch
Ejemplo de salida al ejecutar el script npm run test:unit en un proyecto Vue con Vitest configurado (puede mostrarse como vitest run según tu package.json):

Es recomendable añadir scripts en package.json:
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"test:coverage": "vitest run --coverage"
}
}
Cobertura de codigo
Para generar reportes de cobertura, se instala el proveedor:
npm install -D @vitest/coverage-v8
Y se configura en vite.config.ts:
export default defineConfig({
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'html'],
include: ['src/**/*.ts'],
exclude: ['src/**/*.test.ts', 'src/**/*.d.ts'],
},
},
})
Al ejecutar npx vitest run --coverage, se genera un reporte en la terminal y un directorio coverage/ con el reporte HTML detallado.
Ejemplo práctico completo
Un servicio de validación con su suite de tests:
// validators.ts
export function esEmailValido(email: string): boolean {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return regex.test(email)
}
export function esPasswordSeguro(password: string): boolean {
if (password.length < 8) return false
if (!/[A-Z]/.test(password)) return false
if (!/[a-z]/.test(password)) return false
if (!/[0-9]/.test(password)) return false
return true
}
// validators.test.ts
import { describe, it, expect } from 'vitest'
import { esEmailValido, esPasswordSeguro } from './validators'
describe('esEmailValido', () => {
it('acepta un email con formato correcto', () => {
expect(esEmailValido('user@example.com')).toBe(true)
})
it('rechaza un email sin arroba', () => {
expect(esEmailValido('userexample.com')).toBe(false)
})
it('rechaza un email sin dominio', () => {
expect(esEmailValido('user@')).toBe(false)
})
it('rechaza una cadena vacía', () => {
expect(esEmailValido('')).toBe(false)
})
})
describe('esPasswordSeguro', () => {
it('acepta un password que cumple todos los requisitos', () => {
expect(esPasswordSeguro('MiClave123')).toBe(true)
})
it('rechaza un password menor a 8 caracteres', () => {
expect(esPasswordSeguro('Ab1')).toBe(false)
})
it('rechaza un password sin mayusculas', () => {
expect(esPasswordSeguro('miclave123')).toBe(false)
})
it('rechaza un password sin minúsculas', () => {
expect(esPasswordSeguro('MICLAVE123')).toBe(false)
})
it('rechaza un password sin números', () => {
expect(esPasswordSeguro('MiClaveSinNum')).toBe(false)
})
})
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Vuejs es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Vuejs
Explora más contenido relacionado con Vuejs y continúa aprendiendo con nuestros tutoriales gratuitos.