Snapshot-Testing speichert die Ausgabe eines Werts und vergleicht sie mit zukünftigen Testläufen. Dies ist besonders nützlich für UI-Komponenten, komplexe Objekte oder jede Ausgabe, die konsistent bleiben muss.
Grundlegende Snapshots
Snapshot-Tests werden mit dem .toMatchSnapshot()-Matcher geschrieben:
import { test, expect } from "bun:test";
test("Snapshot", () => {
expect("foo").toMatchSnapshot();
});Beim ersten Ausführen dieses Tests wird das Argument von expect serialisiert und in eine spezielle Snapshot-Datei in einem __snapshots__-Verzeichnis neben der Testdatei geschrieben.
Snapshot-Dateien
Nach dem Ausführen des obigen Tests erstellt Bun:
ihr-projekt/
├── snap.test.ts
└── __snapshots__/
└── snap.test.ts.snapDie Snapshot-Datei enthält:
// Bun Snapshot v1, https://bun.com/docs/test/snapshots
exports[`Snapshot 1`] = `"foo"`;Bei zukünftigen Läufen wird das Argument mit dem Snapshot auf der Festplatte verglichen.
Snapshots aktualisieren
Snapshots können mit dem folgenden Befehl neu generiert werden:
bun test --update-snapshotsDies ist nützlich, wenn:
- Sie die Ausgabe absichtlich geändert haben
- Sie neue Snapshot-Tests hinzufügen
- Die erwartete Ausgabe sich legitim geändert hat
Inline-Snapshots
Für kleinere Werte können Sie Inline-Snapshots mit .toMatchInlineSnapshot() verwenden. Diese Snapshots werden direkt in Ihrer Testdatei gespeichert:
import { test, expect } from "bun:test";
test("Inline-Snapshot", () => {
// Erster Lauf: Snapshot wird automatisch eingefügt
expect({ hello: "world" }).toMatchInlineSnapshot();
});Nach dem ersten Lauf aktualisiert Bun automatisch Ihre Testdatei:
import { test, expect } from "bun:test";
test("Inline-Snapshot", () => {
expect({ hello: "world" }).toMatchInlineSnapshot(`
{
"hello": "world",
}
`);
});Verwendung von Inline-Snapshots
- Schreiben Sie Ihren Test mit
.toMatchInlineSnapshot() - Führen Sie den Test einmal aus
- Bun aktualisiert automatisch Ihre Testdatei mit dem Snapshot
- Bei nachfolgenden Läufen wird der Wert mit dem Inline-Snapshot verglichen
Inline-Snapshots sind besonders nützlich für kleine, einfache Werte, bei denen es hilfreich ist, die erwartete Ausgabe direkt in der Testdatei zu sehen.
Fehler-Snapshots
Sie können auch Fehlermeldungen mit .toThrowErrorMatchingSnapshot() und .toThrowErrorMatchingInlineSnapshot() snapshotieren:
import { test, expect } from "bun:test";
test("Fehler-Snapshot", () => {
expect(() => {
throw new Error("Etwas ist schiefgelaufen");
}).toThrowErrorMatchingSnapshot();
expect(() => {
throw new Error("Ein weiterer Fehler");
}).toThrowErrorMatchingInlineSnapshot();
});Nach dem Ausführen wird die Inline-Version zu:
test("Fehler-Snapshot", () => {
expect(() => {
throw new Error("Etwas ist schiefgelaufen");
}).toThrowErrorMatchingSnapshot();
expect(() => {
throw new Error("Ein weiterer Fehler");
}).toThrowErrorMatchingInlineSnapshot(`"Ein weiterer Fehler"`);
});Fortgeschrittene Snapshot-Verwendung
Komplexe Objekte
Snapshots funktionieren gut mit komplexen verschachtelten Objekten:
import { test, expect } from "bun:test";
test("Komplexes Objekt-Snapshot", () => {
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();
});Array-Snapshots
Arrays sind ebenfalls gut für Snapshot-Testing geeignet:
import { test, expect } from "bun:test";
test("Array-Snapshot", () => {
const numbers = [1, 2, 3, 4, 5].map(n => n * 2);
expect(numbers).toMatchSnapshot();
});Funktionsausgabe-Snapshots
Snapshot die Ausgabe von Funktionen:
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", // Für Tests festgelegt
};
}
test("Berichtsgenerierung", () => {
const data = [
{ id: 1, name: "Alice", age: 30 },
{ id: 2, name: "Bob", age: 25 },
];
expect(generateReport(data)).toMatchSnapshot();
});React-Komponenten-Snapshots
Snapshots sind besonders nützlich für React-Komponenten:
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("Button-Komponenten-Snapshots", () => {
const { container: primary } = render(<Button>Klick mich</Button>);
const { container: secondary } = render(<Button variant="secondary">Abbrechen</Button>);
expect(primary.innerHTML).toMatchSnapshot();
expect(secondary.innerHTML).toMatchSnapshot();
});Property-Matcher
Für Werte, die sich zwischen Testläufen ändern (wie Zeitstempel oder IDs), verwenden Sie Property-Matcher:
import { test, expect } from "bun:test";
test("Snapshot mit dynamischen Werten", () => {
const user = {
id: Math.random(), // Ändert sich bei jedem Lauf
name: "John",
createdAt: new Date().toISOString(), // Ändert sich auch
};
expect(user).toMatchSnapshot({
id: expect.any(Number),
createdAt: expect.any(String),
});
});Der Snapshot speichert:
exports[`Snapshot mit dynamischen Werten 1`] = `
{
"createdAt": Any<String>,
"id": Any<Number>,
"name": "John",
}
`;Benutzerdefinierte Serialisierer
Sie können anpassen, wie Objekte in Snapshots serialisiert werden:
import { test, expect } from "bun:test";
// Benutzerdefinierter Serialisierer für Date-Objekte
expect.addSnapshotSerializer({
test: val => val instanceof Date,
serialize: val => `"${val.toISOString()}"`,
});
test("Benutzerdefinierter Serialisierer", () => {
const event = {
name: "Meeting",
date: new Date("2024-01-01T10:00:00Z"),
};
expect(event).toMatchSnapshot();
});Best Practices
Snapshots klein halten
// Gut: Fokussierte Snapshots
test("Benutzernamen-Formatierung", () => {
const formatted = formatUserName("john", "doe");
expect(formatted).toMatchInlineSnapshot(`"John Doe"`);
});
// Vermeiden: Riesige Snapshots, die schwer zu überprüfen sind
test("Gesamte Seite rendern", () => {
const page = renderEntirePage();
expect(page).toMatchSnapshot(); // Dies könnten Tausende von Zeilen sein
});Beschreibende Testnamen verwenden
// Gut: Klar, was der Snapshot darstellt
test("formatiert Währung mit USD-Symbol", () => {
expect(formatCurrency(99.99)).toMatchInlineSnapshot(`"$99.99"`);
});
// Vermeiden: Unklar, was getestet wird
test("Format-Test", () => {
expect(format(99.99)).toMatchInlineSnapshot(`"$99.99"`);
});Zusammengehörige Snapshots gruppieren
import { describe, test, expect } from "bun:test";
describe("Button-Komponente", () => {
test("Primary-Variante", () => {
expect(render(<Button variant="primary">Klick</Button>))
.toMatchSnapshot();
});
test("Secondary-Variante", () => {
expect(render(<Button variant="secondary">Abbrechen</Button>))
.toMatchSnapshot();
});
test("Deaktivierter Zustand", () => {
expect(render(<Button disabled>Deaktiviert</Button>))
.toMatchSnapshot();
});
});Dynamische Daten handhaben
// Gut: Dynamische Daten normalisieren
test("API-Antwort-Format", () => {
const response = {
data: { id: 1, name: "Test" },
timestamp: Date.now(),
requestId: generateId(),
};
expect({
...response,
timestamp: "TIMESTAMP",
requestId: "REQUEST_ID",
}).toMatchSnapshot();
});
// Oder Property-Matcher verwenden
test("API-Antwort mit Matchern", () => {
const response = getApiResponse();
expect(response).toMatchSnapshot({
timestamp: expect.any(Number),
requestId: expect.any(String),
});
});Snapshots verwalten
Snapshot-Änderungen überprüfen
Wenn sich Snapshots ändern, überprüfen Sie sie sorgfältig:
# Sehen, was sich geändert hat
git diff __snapshots__/
# Aktualisieren, wenn Änderungen beabsichtigt sind
bun test --update-snapshots
# Die aktualisierten Snapshots committen
git add __snapshots__/
git commit -m "Snapshots nach UI-Änderungen aktualisieren"Nicht verwendete Snapshots bereinigen
Bun warnt vor nicht verwendeten Snapshots:
Warning: 1 unused snapshot found:
my-test.test.ts.snap: "alter Test, der nicht mehr existiert 1"Entfernen Sie nicht verwendete Snapshots, indem Sie sie aus den Snapshot-Dateien löschen oder Tests mit Bereinigungs-Flags ausführen, falls verfügbar.
Große Snapshot-Dateien organisieren
Für große Projekte erwägen Sie, Tests zu organisieren, um Snapshot-Dateien überschaubar zu halten:
tests/
├── components/
│ ├── Button.test.tsx
│ └── __snapshots__/
│ └── Button.test.tsx.snap
├── utils/
│ ├── formatters.test.ts
│ └── __snapshots__/
│ └── formatters.test.ts.snapFehlerbehebung
Snapshot-Fehler
Wenn Snapshots fehlschlagen, sehen Sie einen Diff:
- Erwartet
+ Erhalten
Object {
- "name": "John",
+ "name": "Jane",
}Häufige Ursachen:
- Beabsichtigte Änderungen (mit
--update-snapshotsaktualisieren) - Unbeabsichtigte Änderungen (Code reparieren)
- Dynamische Daten (Property-Matcher verwenden)
- Umgebungsunterschiede (Daten normalisieren)
Plattformunterschiede
Achten Sie auf plattformspezifische Unterschiede:
// Pfade können zwischen Windows/Unix unterschiedlich sein
test("Dateioperationen", () => {
const result = processFile("./test.txt");
expect({
...result,
path: result.path.replace(/\\/g, "/"), // Pfade normalisieren
}).toMatchSnapshot();
});Snapshot-Format
Bun verwendet ein einfaches, lesbares Format für Snapshot-Dateien:
// Bun Snapshot v1, https://bun.com/docs/test/snapshots
exports[`Testname 1`] = `Wert`;
exports[`Testname 2`] = `
{
"key": "value",
"nested": {
"array": [1, 2, 3]
}
}
`;Snapshot-Versionierung
Das Snapshot-Format enthält eine Versionsnummer, sodass Bun Snapshot-Dateien bei Formatänderungen migrieren kann. Die aktuelle Version ist v1.
Tipps für effektives Snapshot-Testing
Wann Snapshots verwenden
- UI-Komponenten: Renderingergebnisse von React, Vue, Svelte-Komponenten
- API-Antworten: Struktur und Format von API-Antworten
- Fehlermeldungen: Konsistente Fehlerausgaben
- Datenstrukturen: Komplexe verschachtelte Objekte
Wann Snapshots vermeiden
- Einfache Werte: Verwenden Sie stattdessen reguläre Assertions
- Häufig ändernde Daten: Verwenden Sie Property-Matcher oder normalisieren Sie Daten
- Sehr große Ausgaben: Erwägen Sie, nur relevante Teile zu snapshotieren
Snapshot-Größe überwachen
# Snapshot-Dateigrößen überprüfen
ls -la __snapshots__/
# Große Snapshots identifizieren
find __snapshots__ -name "*.snap" -exec wc -l {} \; | sort -nMigration von Jest
Buns Snapshot-API ist mit Jest kompatibel, sodass die Migration einfach ist:
// Jest
import { test, expect } from "@jest/globals";
test("Jest Snapshot", () => {
expect({ a: 1 }).toMatchSnapshot();
});
// Bun - gleiche Syntax!
import { test, expect } from "bun:test";
test("Bun Snapshot", () => {
expect({ a: 1 }).toMatchSnapshot();
});Die Snapshot-Dateien sind ebenfalls kompatibel, sodass Sie vorhandene Jest-Snapshots in Bun verwenden können.