I test snapshot salvano l'output di un valore e lo confrontano con le future esecuzioni dei test. Questo e particolarmente utile per componenti UI, oggetti complessi o qualsiasi output che deve rimanere coerente.
Snapshot Base
I test snapshot sono scritti usando il matcher .toMatchSnapshot():
import { test, expect } from "bun:test";
test("snap", () => {
expect("foo").toMatchSnapshot();
});La prima volta che questo test viene eseguito, l'argomento di expect sara serializzato e scritto in un file speciale snapshot in una directory __snapshots__ accanto al file di test.
File Snapshot
Dopo aver eseguito il test sopra, Bun creera:
your-project/
├── snap.test.ts
└── __snapshots__/
└── snap.test.ts.snapIl file snapshot contiene:
// Bun Snapshot v1, https://bun.com/docs/test/snapshots
exports[`snap 1`] = `"foo"`;Nelle esecuzioni successive, l'argomento viene confrontato con lo snapshot su disco.
Aggiornare gli Snapshot
Gli snapshot possono essere rigenerati con il seguente comando:
bun test --update-snapshotsQuesto e utile quando:
- Hai intenzionalmente cambiato l'output
- Stai aggiungendo nuovi test snapshot
- L'output previsto e legittimamente cambiato
Snapshot Inline
Per valori piu piccoli, puoi usare snapshot inline con .toMatchInlineSnapshot(). Questi snapshot sono memorizzati direttamente nel tuo file di test:
import { test, expect } from "bun:test";
test("snapshot inline", () => {
// Prima esecuzione: lo snapshot sara inserito automaticamente
expect({ hello: "world" }).toMatchInlineSnapshot();
});Dopo la prima esecuzione, Bun aggiorna automaticamente il tuo file di test:
import { test, expect } from "bun:test";
test("snapshot inline", () => {
expect({ hello: "world" }).toMatchInlineSnapshot(`
{
"hello": "world",
}
`);
});Usare gli Snapshot Inline
- Scrivi il tuo test con
.toMatchInlineSnapshot() - Esegui il test una volta
- Bun aggiorna automaticamente il tuo file di test con lo snapshot 4Nelle esecuzioni successive, il valore sara confrontato con lo snapshot inline
Gli snapshot inline sono particolarmente utili per valori piccoli e semplici dove e utile vedere l'output atteso direttamente nel file di test.
Snapshot di Errori
Puoi anche snapshotare i messaggi di errore usando .toThrowErrorMatchingSnapshot() e .toThrowErrorMatchingInlineSnapshot():
import { test, expect } from "bun:test";
test("snapshot di errore", () => {
expect(() => {
throw new Error("Qualcosa e andato storto");
}).toThrowErrorMatchingSnapshot();
expect(() => {
throw new Error("Un altro errore");
}).toThrowErrorMatchingInlineSnapshot();
});Dopo l'esecuzione, la versione inline diventa:
test("snapshot di errore", () => {
expect(() => {
throw new Error("Qualcosa e andato storto");
}).toThrowErrorMatchingSnapshot();
expect(() => {
throw new Error("Un altro errore");
}).toThrowErrorMatchingInlineSnapshot(`"Un altro errore"`);
});Uso Avanzato degli Snapshot
Oggetti Complessi
Gli snapshot funzionano bene con oggetti nidificati complessi:
import { test, expect } from "bun:test";
test("snapshot oggetto complesso", () => {
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();
});Snapshot di Array
Gli array sono anche adatti per i test snapshot:
import { test, expect } from "bun:test";
test("snapshot array", () => {
const numbers = [1, 2, 3, 4, 5].map(n => n * 2);
expect(numbers).toMatchSnapshot();
});Snapshot dell'Output di Funzioni
Snapshot dell'output delle funzioni:
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", // Fisso per i test
};
}
test("generazione report", () => {
const data = [
{ id: 1, name: "Alice", age: 30 },
{ id: 2, name: "Bob", age: 25 },
];
expect(generateReport(data)).toMatchSnapshot();
});Snapshot di Componenti React
Gli snapshot sono particolarmente utili per i componenti 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("snapshot componenti Button", () => {
const { container: primary } = render(<Button>Click me</Button>);
const { container: secondary } = render(<Button variant="secondary">Cancel</Button>);
expect(primary.innerHTML).toMatchSnapshot();
expect(secondary.innerHTML).toMatchSnapshot();
});Property Matcher
Per valori che cambiano tra le esecuzioni dei test (come timestamp o ID), usa i property matcher:
import { test, expect } from "bun:test";
test("snapshot con valori dinamici", () => {
const user = {
id: Math.random(), // Questo cambia ad ogni esecuzione
name: "John",
createdAt: new Date().toISOString(), // Anche questo cambia
};
expect(user).toMatchSnapshot({
id: expect.any(Number),
createdAt: expect.any(String),
});
});Lo snapshot memorizzera:
exports[`snapshot with dynamic values 1`] = `
{
"createdAt": Any<String>,
"id": Any<Number>,
"name": "John",
}
`;Serializzatori Personalizzati
Puoi personalizzare come gli oggetti vengono serializzati negli snapshot:
import { test, expect } from "bun:test";
// Serializzatore personalizzato per oggetti Date
expect.addSnapshotSerializer({
test: val => val instanceof Date,
serialize: val => `"${val.toISOString()}"`,
});
test("serializzatore personalizzato", () => {
const event = {
name: "Meeting",
date: new Date("2024-01-01T10:00:00Z"),
};
expect(event).toMatchSnapshot();
});Best Practices
Mantieni gli Snapshot Piccoli
// Buono: snapshot focalizzati
test("formattazione nome utente", () => {
const formatted = formatUserName("john", "doe");
expect(formatted).toMatchInlineSnapshot(`"John Doe"`);
});
// Evita: snapshot enormi che sono difficili da revisionare
test("rendering intera pagina", () => {
const page = renderEntirePage();
expect(page).toMatchSnapshot(); // Questo potrebbe essere migliaia di righe
});Usa Nomi Descrittivi dei Test
// Buono: chiaro cosa rappresenta lo snapshot
test("formatta valuta con simbolo USD", () => {
expect(formatCurrency(99.99)).toMatchInlineSnapshot(`"$99.99"`);
});
// Evita: non chiaro cosa viene testato
test("test di formattazione", () => {
expect(format(99.99)).toMatchInlineSnapshot(`"$99.99"`);
});Gruppa Snapshot Correlati
import { describe, test, expect } from "bun:test";
describe("componente Button", () => {
test("variante primary", () => {
expect(render(<Button variant="primary">Click</Button>))
.toMatchSnapshot();
});
test("variante secondary", () => {
expect(render(<Button variant="secondary">Cancel</Button>))
.toMatchSnapshot();
});
test("stato disabled", () => {
expect(render(<Button disabled>Disabled</Button>))
.toMatchSnapshot();
});
});Gestisci i Dati Dinamici
// Buono: normalizza i dati dinamici
test("formato risposta API", () => {
const response = {
data: { id: 1, name: "Test" },
timestamp: Date.now(),
requestId: generateId(),
};
expect({
...response,
timestamp: "TIMESTAMP",
requestId: "REQUEST_ID",
}).toMatchSnapshot();
});
// O usa i property matcher
test("risposta API con matcher", () => {
const response = getApiResponse();
expect(response).toMatchSnapshot({
timestamp: expect.any(Number),
requestId: expect.any(String),
});
});Gestire gli Snapshot
Revisionare i Cambiamenti degli Snapshot
Quando gli snapshot cambiano, revisionali attentamente:
# Vedi cosa e cambiato
git diff __snapshots__/
# Aggiorna se i cambiamenti sono intenzionali
bun test --update-snapshots
# Committa gli snapshot aggiornati
git add __snapshots__/
git commit -m "Update snapshots dopo cambiamenti UI"Pulire gli Snapshot Inutilizzati
Bun avvisera degli snapshot inutilizzati:
Warning: 1 unused snapshot found:
my-test.test.ts.snap: "old test that no longer exists 1"Rimuovi gli snapshot inutilizzati cancellandoli dai file snapshot o eseguendo i test con i flag di pulizia se disponibili.
Organizzare File Snapshot Grandi
Per progetti grandi, considera di organizzare i test per mantenere i file snapshot gestibili:
tests/
├── components/
│ ├── Button.test.tsx
│ └── __snapshots__/
│ └── Button.test.tsx.snap
├── utils/
│ ├── formatters.test.ts
│ └── __snapshots__/
│ └── formatters.test.ts.snapRisoluzione dei Problemi
Fallimenti degli Snapshot
Quando gli snapshot falliscono, vedrai un diff:
- Expected
+ Received
Object {
- "name": "John",
+ "name": "Jane",
}Cause comuni:
- Cambiamenti intenzionali (aggiorna con
--update-snapshots) - Cambiamenti non intenzionali (fix del codice)
- Dati dinamici (usa i property matcher)
- Differenze ambientali (normalizza i dati)
Differenze di Piattaforma
Sii consapevole delle differenze specifiche della piattaforma:
// I percorsi potrebbero differire tra Windows/Unix
test("operazioni file", () => {
const result = processFile("./test.txt");
expect({
...result,
path: result.path.replace(/\\/g, "/"), // Normalizza i percorsi
}).toMatchSnapshot();
});