Vitest Snapshot Testing para React
Cómo usar Vitest snapshot testing para detectar cambios no intencionados en la UI de componentes React, incluyendo inline snapshots y flujos de actualización.
Resumen
El snapshot testing captura la salida renderizada de un componente en un momento dado. En ejecuciones posteriores, Vitest compara la salida actual contra el snapshot almacenado y falla si difieren — alertándote sobre cambios no intencionados en la UI. Vitest es un corredor de tests compatible con Jest para proyectos Vite, con soporte nativo de snapshots. Si venís de Jest, la API es casi idéntica; mirá nuestra recipe de Jest snapshot testing para las diferencias, y el Patrón Snapshot Testing para el patrón general detrás de ambas. Para un tratamiento más profundo del setup completo de Vitest, mirá la guía completa de Vitest + React testing.
Cuándo Usar
- Para detectar regresiones accidentales de CSS o markup en componentes presentacionales.
- Para verificar que un componente renderice la misma estructura luego de un refactor.
- Para testear componentes con salida estable y determinística (sin IDs aleatorios ni timestamps).
- Para documentar la salida esperada de funciones utilitarias que retornan objetos complejos.
Cuándo NO Usar
- Componentes con contenido dinámico (fechas, valores aleatorios, UUIDs) — los snapshots fallarán siempre.
- Para testear lógica de negocio — usá unit tests con aserciones explícitas.
- Componentes que cambian frecuentemente durante desarrollo activo — el churn de snapshots genera ruido.
- Para testear accesibilidad o interacción — los snapshots solo chequean HTML renderizado, no comportamiento.
Solución
Configuración
npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom
// vitest.config.ts
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
test: {
environment: "jsdom",
globals: true,
setupFiles: ["./test/setup.ts"],
},
});
// test/setup.ts
import "@testing-library/jest-dom";
Test de snapshot básico
import { render } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import { Button } from "./Button";
describe("Button", () => {
it("matches snapshot for default variant", () => {
const { container } = render(<Button>Click me</Button>);
expect(container.firstChild).toMatchSnapshot();
});
it("matches snapshot for primary variant", () => {
const { container } = render(<Button variant="primary">Save</Button>);
expect(container.firstChild).toMatchSnapshot();
});
it("matches snapshot for disabled state", () => {
const { container } = render(<Button disabled>Disabled</Button>);
expect(container.firstChild).toMatchSnapshot();
});
});
Inline snapshot
import { render } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import { Badge } from "./Badge";
describe("Badge", () => {
it("renders inline snapshot", () => {
const { container } = render(<Badge count={5} />);
expect(container.firstChild).toMatchInlineSnapshot(`
<span
class="badge ri"
>
5
</span>
`);
});
});
Snapshot con property matchers
import { describe, it, expect } from "vitest";
import { buildApiResponse } from "./api";
describe("buildApiResponse", () => {
it("matches snapshot ignoring dynamic date", () => {
const response = buildApiResponse({ data: [1, 2, 3], status: 200 });
expect(response).toMatchSnapshot({
timestamp: expect.any(String),
reportId: expect.any(String),
});
});
});
Snapshot de salida de función
import { describe, it, expect } from "vitest";
import { formatCurrency } from "./format";
describe("formatCurrency", () => {
it("matches snapshot for USD", () => {
expect(formatCurrency(1234.56, "USD")).toMatchInlineSnapshot(
`"$1,234.56"`,
);
});
it("matches snapshot for EUR", () => {
expect(formatCurrency(1234.56, "EUR")).toMatchInlineSnapshot(
`"€1,234.56"`,
);
});
});
Snapshot de componente async
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import { UserProfile } from "./UserProfile";
describe("UserProfile", () => {
it("matches snapshot after data loads", async () => {
render(<UserProfile userId={42} />);
await screen.findByRole("heading", { name: /alice/i });
expect(document.body).toMatchSnapshot();
});
});
Explicación
toMatchSnapshot() serializa el valor la primera vez que se ejecuta y lo escribe en un
archivo .snap. En ejecuciones posteriores, Vitest compara el valor serializado con la
copia almacenada. Usá toMatchInlineSnapshot() para mantener el valor esperado en el
archivo de test, lo que facilita las revisiones. Los property matchers permiten saltear campos dinámicos — chequeás el tipo, no el valor
exacto.
Yo uso inline snapshots cuando la salida es lo suficientemente chica para leerla en un
diff de PR — un string formateado, un objeto de config chico, el HTML de un componente
simple. Para cosas más grandes, los archivos .snap mantienen el test legible, pero
perdés la revisión a simple vista. El trade-off es real: los inline snapshots hinchan el
archivo de test, pero los .snap son fáciles de saltear en la revisión de código porque
son auto-generados.
Algo que me mordió: los snapshots no atrapan todo. Un componente puede
renderizar el mismo HTML pero comportarse distinto — un botón que dejó de disparar
onClick, un form que submittea al endpoint equivocado, un focus trap que rompe la
navegación por teclado. Los snapshots verifican estructura, no comportamiento. Combinálos
con tests de interacción de
@testing-library/react para cobertura
real.
Variantes
Snapshot con mock calls
import { render, fireEvent } from "@testing-library/react";
import { describe, it, expect, vi } from "vitest";
import { SubmitForm } from "./SubmitForm";
describe("SubmitForm", () => {
it("calls onSubmit with form data", () => {
const onSubmit = vi.fn();
const { getByRole } = render(<SubmitForm onSubmit={onSubmit} />);
fireEvent.click(getByRole("button", { name: /submit/i }));
expect(onSubmit).toHaveBeenCalledWith({
email: "",
password: "",
});
expect(onSubmit.mock.calls).toMatchSnapshot();
});
});
Snapshot de RSC
Para React Server Components, renderizá la salida a string antes de hacer snapshot:
import { renderToString } from "react-dom/server";
import { describe, it, expect } from "vitest";
import { StaticPage } from "./StaticPage";
describe("StaticPage", () => {
it("matches server snapshot", () => {
const html = renderToString(<StaticPage />);
expect(html).toMatchSnapshot();
});
});
Buenas Prácticas
- Mantené los snapshots pequeños — hacé snapshot de un componente, no de todo el árbol de una página. Me mordió tener snapshots a nivel de página que se rompían en cada tweak de CSS.
- Usá inline snapshots para salidas pequeñas y estables, así son revisables en PRs.
- Revisá los diffs de snapshots en PRs — un test verde con un snapshot modificado significa
que alguien aprobó el cambio. Bloqueo PRs donde los
.snapcambian sin un comentario que explique por qué. - Usá property matchers para campos dinámicos —
expect.any(String)oexpect.any(Date)te salva del flakiness de timestamps. - Ejecutá
vitest -upara actualizar snapshots solo después de verificar que el cambio es intencional. - No hagas snapshot de componentes con IDs aleatorios, timestamps o clases generadas.
Errores Comunes
- Actualizar snapshots a ciegas — ejecutar
vitest -usin revisar el diff oculta regresiones reales. - Hacer snapshot de todo — un snapshot de 200 líneas es ilegible y falla ante cualquier cambio menor de CSS. Dividilo en snapshots de componentes más pequeños.
- No usar property matchers para datos dinámicos — sin ellos, un timestamp en la salida rompe el snapshot en cada ejecución.
- Guardar snapshots lejos de los tests — mantené los archivos
.snapjunto a los tests para facilitar su descubrimiento. - Usar snapshots como único test — los snapshots verifican estructura, no comportamiento.
Agregá tests de interacción con
@testing-library/react.
See Also
- Documentación de Vitest Snapshot — guía
oficial cubriendo
toMatchSnapshot,toMatchInlineSnapshoty configuración de snapshots. - React Testing Library docs — la forma recomendada de renderizar componentes React en tests.
- Jest snapshot testing — la API original de snapshot testing que Vitest extiende.
- Recipe de Jest snapshot testing — nuestra recipe para proyectos Jest que no migraron a Vitest.
- Guía completa de Vitest + React — setup completo incluyendo mocking, coverage e integración con CI.
Preguntas frecuentes
¿Cómo actualizo los snapshots después de un cambio intencional?
Ejecutá npx vitest -u (o --update). Esto regenera todos los archivos .snap. Revisá el
diff en Git antes de commitear.
¿Qué diferencia hay entre toMatchSnapshot y toMatchInlineSnapshot?
toMatchSnapshot escribe en un archivo .snap separado. toMatchInlineSnapshot escribe el
snapshot directamente en el archivo de test como un string literal. Los inline snapshots son
más revisables en PRs, pero pueden hinchar el archivo de test para salidas grandes.
¿Cómo ignoro valores dinámicos en los snapshots?
Usá property matchers:
expect(result).toMatchSnapshot({
id: expect.any(String),
createdAt: expect.any(String),
});
Vitest va a matchear la estructura pero ignorar los valores reales de esos campos.
¿Debería commitear archivos .snap en Git?
Sí. Los archivos de snapshot deben commitearse y revisarse en PRs. Sirven como contrato de la salida esperada.
¿Puedo usar snapshot testing con React Server Components?
Sí. Usá renderToString de react-dom/server y hacé snapshot de la salida HTML. Los tests
de renderizado del lado del cliente usan @testing-library/react como siempre.
¿Por qué ocurre el drift de snapshots en suites grandes?
Usá toMatchInlineSnapshot para salidas pequeñas así el valor esperado se ve en la
revisión de código. Para archivos .snap, habilitá --ci en CI para fallar ante snapshots
desactualizados en lugar de escribir nuevos silenciosamente. Ejecutá vitest -u solo de
forma local después de verificar que el cambio es intencional.
Recursos Relacionados
Snapshot Testing de Componentes React con Jest
Como usar snapshot testing de Jest para detectar regresiones de UI no intencionales en componentes React y prevenir bugs visuales en produccion
RecipeTestear APIs Express con supertest
Cómo testear endpoints REST de Express.js end-to-end usando supertest, incluyendo códigos de estado, bodies JSON, headers, autenticación y manejo de errores.
RecipeCuándo Usar useMemo y useCallback
Cómo y cuándo usar los hooks useMemo y useCallback de React para optimización de performance, y cuándo añaden overhead innecesario.
GuideVitest para React: Component, Hook, e Integration Testing
Dominá Vitest para testing en React: component tests con Testing Library, hook tests con renderHook, integration tests, mocking, snapshot testing y ejecución paralela.
RecipeMockear Peticiones de Red con MSW
Cómo usar Mock Service Worker (MSW) para interceptar peticiones de red en tests y desarrollo de JavaScript, incluyendo mocking de REST y GraphQL.
RecipeGenerar Datos de Test (Faker + Factories)
Cómo generar datos de test realistas y deterministas con Faker, factory-boy y generadores type-aware para suites de test confiables en Python, JavaScript y Java.