Las pruebas de snapshot guardan la salida de un valor y la comparan con ejecuciones futuras de pruebas. Esto es particularmente útil para componentes UI, objetos complejos o cualquier salida que necesite permanecer consistente.
Snapshots básicos
Las pruebas de snapshot se escriben usando el matcher .toMatchSnapshot():
import { test, expect } from "bun:test";
test("snap", () => {
expect("foo").toMatchSnapshot();
});La primera vez que se ejecuta esta prueba, el argumento a expect se serializará y escribirá en un archivo de snapshot especial en un directorio __snapshots__ junto al archivo de prueba.
Archivos de snapshot
Después de ejecutar la prueba anterior, Bun creará:
tu-proyecto/
├── snap.test.ts
└── __snapshots__/
└── snap.test.ts.snapEl archivo de snapshot contiene:
// Bun Snapshot v1, https://bun.com/docs/test/snapshots
exports[`snap 1`] = `"foo"`;En ejecuciones futuras, el argumento se compara con el snapshot en disco.
Actualizar snapshots
Los snapshots pueden regenerarse con el siguiente comando:
bun test --update-snapshotsEsto es útil cuando:
- Has cambiado intencionalmente la salida
- Estás agregando nuevas pruebas de snapshot
- La salida esperada ha cambiado legítimamente
Snapshots en línea
Para valores más pequeños, puedes usar snapshots en línea con .toMatchInlineSnapshot(). Estos snapshots se almacenan directamente en tu archivo de prueba:
import { test, expect } from "bun:test";
test("snapshot en línea", () => {
// Primera ejecución: el snapshot se insertará automáticamente
expect({ hello: "world" }).toMatchInlineSnapshot();
});Después de la primera ejecución, Bun actualiza automáticamente tu archivo de prueba:
import { test, expect } from "bun:test";
test("snapshot en línea", () => {
expect({ hello: "world" }).toMatchInlineSnapshot(`
{
"hello": "world",
}
`);
});Usar snapshots en línea
- Escribe tu prueba con
.toMatchInlineSnapshot() - Ejecuta la prueba una vez
- Bun actualiza automáticamente tu archivo de prueba con el snapshot
- En ejecuciones posteriores, el valor se comparará con el snapshot en línea
Los snapshots en línea son particularmente útiles para valores pequeños y simples donde es útil ver la salida esperada directamente en el archivo de prueba.
Snapshots de errores
También puedes hacer snapshot de mensajes de error usando .toThrowErrorMatchingSnapshot() y .toThrowErrorMatchingInlineSnapshot():
import { test, expect } from "bun:test";
test("snapshot de error", () => {
expect(() => {
throw new Error("Algo salió mal");
}).toThrowErrorMatchingSnapshot();
expect(() => {
throw new Error("Otro error");
}).toThrowErrorMatchingInlineSnapshot();
});Después de ejecutar, la versión en línea se convierte en:
test("snapshot de error", () => {
expect(() => {
throw new Error("Algo salió mal");
}).toThrowErrorMatchingSnapshot();
expect(() => {
throw new Error("Otro error");
}).toThrowErrorMatchingInlineSnapshot(`"Otro error"`);
});Uso avanzado de snapshots
Objetos complejos
Los snapshots funcionan bien con objetos anidados complejos:
import { test, expect } from "bun:test";
test("snapshot de objeto complejo", () => {
const user = {
id: 1,
name: "John Doe",
email: "john@example.com",
profile: {
age: 30,
preferences: {
theme: "dark",
notifications: true,
},
},
tags: ["developer", "javascript", "bun"],
};
expect(user).toMatchSnapshot();
});Snapshots de arrays
Los arrays también son adecuados para pruebas de snapshot:
import { test, expect } from "bun:test";
test("snapshot de array", () => {
const numbers = [1, 2, 3, 4, 5].map(n => n * 2);
expect(numbers).toMatchSnapshot();
});Snapshots de salida de funciones
Haz snapshot de la salida de funciones:
import { test, expect } from "bun:test";
function generateReport(data: any[]) {
return {
total: data.length,
summary: data.map(item => ({ id: item.id, name: item.name })),
timestamp: "2024-01-01", // Fijo para pruebas
};
}
test("generación de informe", () => {
const data = [
{ id: 1, name: "Alice", age: 30 },
{ id: 2, name: "Bob", age: 25 },
];
expect(generateReport(data)).toMatchSnapshot();
});Snapshots de componentes React
Los snapshots son particularmente útiles para componentes React:
import { test, expect } from "bun:test";
import { render } from "@testing-library/react";
function Button({ children, variant = "primary" }) {
return <button className={`btn btn-${variant}`}>{children}</button>;
}
test("snapshots de componente Button", () => {
const { container: primary } = render(<Button>Haz clic aquí</Button>);
const { container: secondary } = render(<Button variant="secondary">Cancelar</Button>);
expect(primary.innerHTML).toMatchSnapshot();
expect(secondary.innerHTML).toMatchSnapshot();
});Property Matchers
Para valores que cambian entre ejecuciones de pruebas (como marcas de tiempo o IDs), usa property matchers:
import { test, expect } from "bun:test";
test("snapshot con valores dinámicos", () => {
const user = {
id: Math.random(), // Esto cambia en cada ejecución
name: "John",
createdAt: new Date().toISOString(), // Esto también cambia
};
expect(user).toMatchSnapshot({
id: expect.any(Number),
createdAt: expect.any(String),
});
});El snapshot almacenará:
exports[`snapshot con valores dinámicos 1`] = `
{
"createdAt": Any<String>,
"id": Any<Number>,
"name": "John",
}
`;Serializadores personalizados
Puedes personalizar cómo se serializan los objetos en snapshots:
import { test, expect } from "bun:test";
// Serializador personalizado para objetos Date
expect.addSnapshotSerializer({
test: val => val instanceof Date,
serialize: val => `"${val.toISOString()}"`,
});
test("serializador personalizado", () => {
const event = {
name: "Reunión",
date: new Date("2024-01-01T10:00:00Z"),
};
expect(event).toMatchSnapshot();
});Mejores prácticas
Mantener snapshots pequeños
// Bueno: Snapshots enfocados
test("formateo de nombre de usuario", () => {
const formatted = formatUserName("john", "doe");
expect(formatted).toMatchInlineSnapshot(`"John Doe"`);
});
// Evitar: Snapshots enormes difíciles de revisar
test("renderizado de página completa", () => {
const page = renderEntirePage();
expect(page).toMatchSnapshot(); // Esto podría ser miles de líneas
});Usar nombres de prueba descriptivos
// Bueno: Claro qué representa el snapshot
test("formatea moneda con símbolo USD", () => {
expect(formatCurrency(99.99)).toMatchInlineSnapshot(`"$99.99"`);
});
// Evitar: No claro qué se está probando
test("prueba de formato", () => {
expect(format(99.99)).toMatchInlineSnapshot(`"$99.99"`);
});Agrupar snapshots relacionados
import { describe, test, expect } from "bun:test";
describe("componente Button", () => {
test("variante primary", () => {
expect(render(<Button variant="primary">Clic</Button>))
.toMatchSnapshot();
});
test("variante secondary", () => {
expect(render(<Button variant="secondary">Cancelar</Button>))
.toMatchSnapshot();
});
test("estado deshabilitado", () => {
expect(render(<Button disabled>Deshabilitado</Button>))
.toMatchSnapshot();
});
});Manejar datos dinámicos
// Bueno: Normalizar datos dinámicos
test("formato de respuesta de API", () => {
const response = {
data: { id: 1, name: "Test" },
timestamp: Date.now(),
requestId: generateId(),
};
expect({
...response,
timestamp: "TIMESTAMP",
requestId: "REQUEST_ID",
}).toMatchSnapshot();
});
// O usar property matchers
test("respuesta de API con matchers", () => {
const response = getApiResponse();
expect(response).toMatchSnapshot({
timestamp: expect.any(Number),
requestId: expect.any(String),
});
});Gestionar snapshots
Revisar cambios de snapshots
Cuando los snapshots cambian, revísalos cuidadosamente:
# Ver qué cambió
git diff __snapshots__/
# Actualizar si los cambios son intencionales
bun test --update-snapshots
# Confirmar los snapshots actualizados
git add __snapshots__/
git commit -m "Actualizar snapshots después de cambios de UI"Limpiar snapshots no usados
Bun advertirá sobre snapshots no usados:
Warning: 1 unused snapshot found:
my-test.test.ts.snap: "old test that no longer exists 1"Elimina snapshots no usados eliminándolos de los archivos de snapshot o ejecutando pruebas con banderas de limpieza si están disponibles.
Organizar archivos de snapshot grandes
Para proyectos grandes, considera organizar pruebas para mantener los archivos de snapshot manejables:
tests/
├── components/
│ ├── Button.test.tsx
│ └── __snapshots__/
│ └── Button.test.tsx.snap
├── utils/
│ ├── formatters.test.ts
│ └── __snapshots__/
│ └── formatters.test.ts.snapSolución de problemas
Fallos de snapshot
Cuando los snapshots fallan, verás un diff:
- Expected
+ Received
Object {
- "name": "John",
+ "name": "Jane",
}Causas comunes:
- Cambios intencionales (actualiza con
--update-snapshots) - Cambios no intencionales (arregla el código)
- Datos dinámicos (usa property matchers)
- Diferencias de entorno (normaliza los datos)
Diferencias de plataforma
Ten en cuenta diferencias específicas de plataforma:
// Las rutas pueden diferir entre Windows/Unix
test("operaciones de archivo", () => {
const result = processFile("./test.txt");
expect({
...result,
path: result.path.replace(/\\/g, "/"), // Normalizar rutas
}).toMatchSnapshot();
});