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.
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 rr"
>
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 ignorar
campos dinámicos chequeando el tipo en lugar del valor exacto.
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.
- 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.
- Usá property matchers para campos dinámicos como
expect.any(String)oexpect.any(Date). - 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 — si la salida incluye un timestamp, el snapshot fallará 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.
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.
¿Cómo evito 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
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.