Snapshot Testing con Vitest: Detecta Cambios Inesperados en tu UI antes de que Lleguen a Producción

Aprende a implementar snapshot testing con Vitest para capturar el estado de tus componentes y detectar cambios no deseados en la interfaz de usuario de forma automática.

Uno de los problemas más comunes en equipos de frontend es que un cambio aparentemente inocente en un componente rompe la UI de otra parte de la aplicación sin que nadie lo note hasta que llega a producción. El snapshot testing es una técnica sencilla y muy efectiva para evitar exactamente eso, y con Vitest se integra de forma natural en proyectos modernos con Vite.

¿Qué es el Snapshot Testing?

La idea es simple: la primera vez que ejecutas un test, Vitest renderiza el componente (o cualquier estructura de datos serializable) y guarda una «foto» de su salida en un archivo .snap. En cada ejecución posterior, compara el resultado actual con esa foto guardada. Si hay diferencias, el test falla y te avisa del cambio.

Esto no reemplaza los tests unitarios o de integración clásicos, pero cubre un ángulo diferente: detectar regresiones visuales o estructurales a nivel de código, sin necesidad de un navegador completo ni herramientas de comparación de imágenes.

Configuración inicial: Vitest en un proyecto Vite

Si ya tienes un proyecto con Vite, añadir Vitest es cuestión de minutos:

npm install -D vitest @testing-library/react jsdom

Luego añade en tu vite.config.ts (o .js):

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  test: {
    environment: 'jsdom',
    globals: true,
  },
})

Con esto, Vitest ya está listo para ejecutar tests en un entorno simulado de DOM.

Tu primer snapshot test

Supón que tienes un componente Badge.tsx que muestra una etiqueta de estado:

// Badge.tsx
export function Badge({ label, type }: { label: string; type: 'success' | 'error' | 'warning' }) {
  return (
    <span className={`badge badge--${type}`}>{label}</span>
  )
}

El test de snapshot sería así:

// Badge.test.tsx
import { render } from '@testing-library/react'
import { expect, test } from 'vitest'
import { Badge } from './Badge'

test('Badge success renderiza correctamente', () => {
  const { container } = render(<Badge label="OK" type="success" />)
  expect(container).toMatchSnapshot()
})

test('Badge error renderiza correctamente', () => {
  const { container } = render(<Badge label="Error" type="error" />)
  expect(container).toMatchSnapshot()
})

La primera vez que corres npx vitest, Vitest crea un archivo __snapshots__/Badge.test.tsx.snap con el HTML serializado de cada test. A partir de ahí, cualquier cambio en la estructura del componente hará fallar el test.

Cómo actualizar snapshots deliberadamente

Cuando haces un cambio intencionado en el componente (por ejemplo, añades un atributo aria-label), el test fallará porque el snapshot antiguo ya no coincide. Esto es correcto: el sistema te está pidiendo que confirmes el cambio.

Para actualizar los snapshots después de revisar que el cambio es esperado:

npx vitest --update-snapshots

O en modo interactivo:

npx vitest -u

Una buena práctica es incluir siempre la actualización de snapshots en el pull request junto con el diff del componente, para que el reviewer pueda ver exactamente qué cambió.

Inline Snapshots: snapshots dentro del propio test

Vitest soporta también los inline snapshots, donde la «foto» se guarda directamente en el archivo de test en lugar de en un archivo .snap separado. Resultan útiles cuando los snapshots son pequeños y quieres tenerlo todo en un solo lugar:

test('Badge warning tiene la clase correcta', () => {
  const { container } = render(<Badge label="Atención" type="warning" />)
  expect(container.firstChild).toMatchInlineSnapshot(`
    <span
      class="badge badge--warning"
    >
      Atención
    </span>
  `)
})

Snapshot testing de datos, no solo de UI

Una ventaja que muchos QAs pasan por alto: los snapshots no son solo para componentes. Puedes usarlos para capturar la salida de funciones de transformación de datos, respuestas de API mockeadas o cualquier objeto JSON complejo:

import { parseApiResponse } from './utils'

test('parseApiResponse transforma los datos correctamente', () => {
  const rawData = { id: 1, first_name: 'Ana', last_name: 'García', active: true }
  expect(parseApiResponse(rawData)).toMatchSnapshot()
})

Esto es especialmente útil cuando tienes funciones de mapeo o normalización de datos que se usan en muchos sitios: si alguien cambia la lógica, los snapshots lo detectan de inmediato.

Cuándo NO usar snapshot testing

El snapshot testing tiene limitaciones claras que conviene conocer para no abusar de él:

  • Componentes con datos dinámicos (timestamps, IDs aleatorios, datos en tiempo real): el snapshot cambiará en cada ejecución. En estos casos, usa expect.any() o mockea los valores variables.
  • Componentes muy grandes o complejos: un snapshot de 500 líneas de HTML es difícil de revisar en un PR. Divide los tests en unidades más pequeñas.
  • Como sustituto de tests de comportamiento: un snapshot te dice que algo cambió estructuralmente, pero no que la funcionalidad sigue correcta. Combínalo siempre con tests de comportamiento.

Integración en el pipeline de CI

En tu pipeline (GitHub Actions, GitLab CI, etc.), añade el comando de Vitest sin la flag de actualización para que los snapshots fallen si hay diferencias no confirmadas:

# .github/workflows/test.yml
- name: Run tests
  run: npx vitest run

Si un desarrollador olvida actualizar los snapshots después de un cambio intencional, el pipeline fallará y lo recordará antes del merge. Ese es exactamente el comportamiento que queremos.

Conclusión

El snapshot testing con Vitest es una herramienta ligera y de bajo coste de mantenimiento para atrapar regresiones en componentes y lógica de transformación de datos. No requiere configuración compleja y se integra perfectamente en flujos de trabajo modernos con Vite y React (o Vue, Svelte, etc.). Úsalo como complemento de tus tests de comportamiento y tendrás una capa extra de seguridad que avisa cuando algo cambia sin que nadie lo haya pedido.

JEscorcia
JEscorcia