Cambiar tema

Pruebas unitarias con Vitest: de la configuración al flujo TDD

Easton editorial illustration: one large test card moving through a three-stage TDD loop

¿Cuánto tardas en configurar Jest en un proyecto ESM? ts-jest, babel-jest, jest.config.js… y problemas de resolución de módulos. Yo pasé una tarde entera para que Jest reconociera imports de archivos .vue.

Vitest necesita una línea de configuración.

No es exageración. La primera vez que ejecuté vitest en un proyecto Vite, los tests terminaron en segundos —como cambiar un PC lento de tres años por uno nuevo.

¿Qué tan rápido es Vitest? Datos oficiales y de la comunidad: arranque en frío ~200 ms (Jest 2-4 s), 500 tests en ~8 s (Jest ~45 s). Comparte config con Vite, TypeScript nativo, API casi igual a Jest —migración en media hora.

Este artículo va de configuración cero al flujo TDD completo, mocking y cobertura. Tanto si empiezas con Vitest como si migras desde Jest, aquí tienes lo que necesitas.

¿Qué es Vitest y por qué es tan rápido?

En pocas palabras, Vitest es el framework de pruebas nativo de Vite.

Si ya usas Vite, Vitest es casi «plug and play». Hereda alias, variables de entorno y CSS —sin transform, moduleFileExtensions ni moduleNameMapper de Jest.

Tres ventajas clave:

Velocidad. Arranque ~200 ms vs 2-4 s de Jest. En proyectos grandes: 500 tests ~8 s vs ~45 s (DEV Community 2026). La diferencia es enorme.

Compatibilidad con Jest. describe, it, expect, vi.fn() —misma sintaxis. Migrar suele ser cambiar imports.

Watch inteligente. «HMR for tests»: al cambiar código, solo reejecuta tests relacionados. Feedback instantáneo en desarrollo.

Instalación y configuración

Requisitos: Vite >= 6.0.0, Node >= 20.0.0.

Instalación

Un comando:

npm install -D vitest

Sin @types/jest, ts-jest, jest-environment-jsdom… TypeScript nativo.

Archivo de configuración

En vite.config.ts o un vitest.config.ts aparte.

Proyecto simple —vite.config.ts:

// vite.config.ts
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    globals: true,  // describe, it, expect globales
    environment: 'node', // o 'jsdom' para navegador
    include: ['tests/**/*.test.ts'],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'html', 'lcov'],
    },
  },
})

globals: true evita importar en cada archivo, como en Jest.

Para DOM (componentes), environment: 'jsdom' e instala jsdom:

npm install -D jsdom

Scripts

En package.json:

{
  "scripts": {
    "test": "vitest",
    "test:run": "vitest run"
  }
}

npm test = watch; npm run test:run = una ejecución (CI).

Mucho más simple que preset, transform y moduleFileExtensions de Jest.

Escribir pruebas unitarias

Nombres .test.ts o .spec.ts en tests/ o junto al código fuente.

Estructura básica

import { describe, it, expect } from 'vitest'
import { add, divide } from './math'

describe('Math utilities', () => {
  it('should add two numbers', () => {
    expect(add(2, 3)).toBe(5)
  })

  it('should throw on division by zero', () => {
    expect(() => divide(10, 0)).toThrow('Division by zero')
  })
})

Con globals: true, puedes omitir el import.

Aserciones habituales

expect(value).toBe(5)
expect(obj).toEqual({ a: 1 })
expect(value).toBeTruthy()
expect(value).toBeFalsy()
expect(value).toBeNull()
expect(() => fn()).toThrow()
expect(() => fn()).toThrow('Error message')
expect(n).toBeGreaterThan(10)
expect(n).toBeLessThanOrEqual(5)
expect(arr).toContain('item')
expect(str).toMatch(/pattern/)

Filtrar tests

Solo uno: it.only(...). Saltar: it.skip(...). También en describe.

En watch, ver verde o rojo al instante —mucho mejor que esperar segundos en cada arranque de Jest.

Flujo TDD en la práctica

TDD: prueba primero, código después.

Suena al revés, pero obliga a definir qué debe hacer la función antes de implementarla —no tests de relleno tras el código.

Ejemplo: validateEmail.

Paso 1: prueba sin implementación

// tests/validateEmail.test.ts
import { describe, it, expect } from 'vitest'
import { validateEmail } from '../src/validateEmail'

describe('validateEmail', () => {
  it('should return true for valid email', () => {
    expect(validateEmail('[email protected]')).toBe(true)
  })

  it('should return false for invalid email', () => {
    expect(validateEmail('invalid')).toBe(false)
  })
})

La función no existe; la prueba debe fallar. Eso es el primer paso TDD.

Paso 2: mínimo código

// src/validateEmail.ts
export function validateEmail(email: string): boolean {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
}

npm test —ambas en verde.

Paso 3: más bordes

it('should return false for empty string', () => {
  expect(validateEmail('')).toBe(false)
})

it('should return false for email without domain', () => {
  expect(validateEmail('user@')).toBe(false)
})

it('should return false for email with spaces', () => {
  expect(validateEmail('test @example.com')).toBe(false)
})

Paso 4: refactor

const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/

export function validateEmail(email: string): boolean {
  if (!email || email.trim() === '') {
    return false
  }
  return EMAIL_REGEX.test(email)
}

Tests siguen en verde; refactor con red de seguridad.

¿Por qué funciona TDD?

  1. Diseño antes de código
  2. Iteración rápida con watch
  3. Refactor seguro

Empieza con utilidades simples; luego lógica más compleja.

Técnicas avanzadas de Mocking

A menudo hay que simular APIs, BD o librerías. La API de Vitest es similar a Jest; el objeto central es vi.

vi.fn()

import { vi, describe, it, expect } from 'vitest'

describe('vi.fn() demo', () => {
  it('tracks calls', () => {
    const mockFn = vi.fn()

    mockFn('hello')
    mockFn('world')

    expect(mockFn).toHaveBeenCalledTimes(2)
    expect(mockFn).toHaveBeenNthCalledWith(1, 'hello')
  })
})

Valores de retorno:

const mockFn = vi.fn().mockReturnValue('mocked result')
const asyncMock = vi.fn().mockResolvedValue({ data: 'ok' })

vi.mock()

import { vi, describe, it, expect, beforeEach } from 'vitest'
import { fetchUser } from './api'
import { UserService } from './UserService'

vi.mock('./api', () => ({
  fetchUser: vi.fn().mockResolvedValue({ id: 1, name: 'Alice' })
}))

describe('UserService', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  it('should fetch user', async () => {
    const service = new UserService()
    const user = await service.getUser(1)

    expect(user.name).toBe('Alice')
    expect(fetchUser).toHaveBeenCalledWith(1)
  })
})

vi.mock() va arriba del archivo.

vi.spyOn()

import { vi, describe, it, expect, afterEach } from 'vitest'
import { calculator } from './calculator'

describe('spyOn demo', () => {
  afterEach(() => {
    vi.restoreAllMocks()
  })

  it('tracks add calls', () => {
    const addSpy = vi.spyOn(calculator, 'add')

    const result = calculator.add(2, 3)

    expect(result).toBe(5)
    expect(addSpy).toHaveBeenCalledWith(2, 3)
  })
})

La función real se ejecuta; solo se registra la llamada.

Mock de objetos globales

vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
  ok: true,
  json: () => Promise.resolve({ data: 'mocked' })
}))

También window, localStorage, etc.

Empieza con vi.fn(); luego vi.mock(). Limpia mocks entre tests.

Cobertura y buenas prácticas

Vitest soporta v8 (rápido) e istanbul. v8 suele bastar.

Configurar cobertura

test: {
  coverage: {
    provider: 'v8',
    reporter: ['text', 'html', 'lcov'],
    thresholds: {
      lines: 80,
      functions: 80,
      branches: 70,
    },
    exclude: ['node_modules/', 'tests/', '**/*.d.ts'],
  },
}
vitest run --coverage

Informe en terminal y carpeta coverage/ con HTML.

Umbrales

Si no alcanzas thresholds, Vitest falla —útil en CI. 80% es un buen inicio; 100% suele ser contraproducente.

CI/CD

# .github/workflows/test.yml
- name: Run tests with coverage
  run: npm run test:run -- --coverage

Sube lcov a Codecov o Coveralls.

Consejos

  1. No persigas el 100%
  2. Prioriza el camino crítico
  3. Elimina tests obsoletos
  4. Mantén vitest en watch al desarrollar

Resumen

Vitest: rápido, simple, agradable de usar.

~200 ms de arranque, 500 tests en ~8 s. Config compartida con Vite. API como Jest.

Si usas Vite, Vitest es la opción natural —olvida los dolores de ESM en Jest.

Si aún tienes Jest, prueba media hora de migración: instala, cambia imports, la mayoría corre directo.

TDD: empieza con funciones simples, prueba primero. Acostumbrarse cambia la forma de trabajar.

Las pruebas no son carga; son seguridad. Configura Vitest bien y escribe con más tranquilidad.

Configuración de Vitest y flujo TDD

Pasos completos para configurar Vitest y practicar desarrollo guiado por pruebas

⏱️ Estimated time: 30 min

  1. 1

    Step 1: Instalar Vitest

    Ejecuta el comando de instalación:

    ```bash
    npm install -D vitest
    ```

    Requisitos: Vite >= 6.0.0, Node >= 20.0.0
  2. 2

    Step 2: Configurar vite.config.ts

    Añade el campo test en la configuración:

    ```typescript
    import { defineConfig } from 'vitest/config'

    export default defineConfig({
    test: {
    globals: true,
    environment: 'node',
    include: ['tests/**/*.test.ts'],
    },
    })
    ```

    globals: true evita importar describe, it y expect en cada archivo.
  3. 3

    Step 3: Añadir scripts de ejecución

    En package.json:

    ```json
    {
    "scripts": {
    "test": "vitest",
    "test:run": "vitest run"
    }
    }
    ```

    npm test en modo watch; npm run test:run para una sola ejecución (CI).
  4. 4

    Step 4: Escribir la primera prueba

    Crea tests/math.test.ts:

    ```typescript
    import { describe, it, expect } from 'vitest'

    describe('Math', () => {
    it('should add numbers', () => {
    expect(1 + 1).toBe(2)
    })
    })
    ```

    Ejecuta npm test para verificar la configuración.
  5. 5

    Step 5: Practicar el flujo TDD

    Sigue el desarrollo guiado por pruebas:

    • Paso 1: escribe la prueba y define el comportamiento esperado
    • Paso 2: implementa el mínimo código para pasar
    • Paso 3: añade pruebas de bordes
    • Paso 4: refactoriza

    El modo watch de Vitest da feedback en segundos.
  6. 6

    Step 6: Configurar Coverage

    Añade configuración de cobertura:

    ```typescript
    coverage: {
    provider: 'v8',
    reporter: ['text', 'html'],
    thresholds: {
    lines: 80,
    functions: 80,
    },
    }
    ```

    Ejecuta vitest run --coverage para generar el informe.

FAQ

¿Qué diferencia hay entre Vitest y Jest?
Vitest es nativo de Vite, comparte configuración; arranque en frío ~200 ms (Jest 2-4 s). API casi idéntica a Jest; migración sencilla. Ventajas: velocidad, configuración simple, TypeScript nativo.
¿Cómo migrar de Jest a Vitest?
Pasos simples:

• Desinstala paquetes de Jest e instala vitest
• Migra jest.config.js a vite.config.ts
• Cambia imports de 'jest' a 'vitest'
• Sustituye jest.fn()/jest.mock() por vi.fn()/vi.mock()

En la mayoría de casos, 30 minutos bastan.
¿Qué entornos de prueba soporta Vitest?
Varios: node (por defecto, backend), jsdom (DOM del navegador), happy-dom (alternativa más rápida). Define environment en la config; para componentes de navegador instala jsdom.
¿Cómo hacer mock de peticiones API en Vitest?
Tres formas habituales:

• vi.fn(): mock de una función con valor de retorno
• vi.mock(): mock de un módulo completo
• vi.spyOn(): vigila llamadas sin reemplazar la implementación

Tras cada prueba usa vi.clearAllMocks() o vi.restoreAllMocks().
¿Cómo configurar Coverage en Vitest?
En test.coverage de vite.config.ts: provider (v8 recomendado), reporter (text/html/lcov) y thresholds. vitest run --coverage genera el informe; si no alcanzas el umbral, falla —útil en CI.
¿Cuál es el núcleo del flujo TDD?
El ciclo rojo-verde-refactor:

• Rojo: prueba que falla
• Verde: código mínimo que pasa
• Refactor: mejora la estructura

Con el modo watch de Vitest, feedback en segundos. Escribir primero la prueba aclara el diseño de la función.

6 min de lectura · Publicado el: 14 abr 2026 · Actualizado el: 21 ago 2026

Comentarios

Inicia sesión con GitHub para dejar un comentario

Easton BlogEaston Blog