Skip to content

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:

test.ts
ts
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:

Verzeichnisstruktur
text
ihr-projekt/
├── snap.test.ts
└── __snapshots__/
    └── snap.test.ts.snap

Die Snapshot-Datei enthält:

__snapshots__/snap.test.ts.snap
ts
// 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:

bash
bun test --update-snapshots

Dies 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:

test.ts
ts
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:

test.ts
ts
import { test, expect } from "bun:test";

test("Inline-Snapshot", () => {
  expect({ hello: "world" }).toMatchInlineSnapshot(`
{
  "hello": "world",
}
`);
});

Verwendung von Inline-Snapshots

  1. Schreiben Sie Ihren Test mit .toMatchInlineSnapshot()
  2. Führen Sie den Test einmal aus
  3. Bun aktualisiert automatisch Ihre Testdatei mit dem Snapshot
  4. 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:

test.ts
ts
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.ts
ts
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:

test.ts
ts
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:

test.ts
ts
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:

test.ts
ts
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:

test.ts
tsx
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:

test.ts
ts
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:

Snapshot-Datei
txt
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:

test.ts
ts
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

test.ts
ts
// 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

test.ts
ts
// 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

test.ts
ts
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

test.ts
ts
// 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:

bash
# 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:

Warnung
txt
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:

Verzeichnisstruktur
text
tests/
├── components/
│   ├── Button.test.tsx
│   └── __snapshots__/
│       └── Button.test.tsx.snap
├── utils/
│   ├── formatters.test.ts
│   └── __snapshots__/
│       └── formatters.test.ts.snap

Fehlerbehebung

Snapshot-Fehler

Wenn Snapshots fehlschlagen, sehen Sie einen Diff:

diff
diff
- Erwartet
+ Erhalten

  Object {
-   "name": "John",
+   "name": "Jane",
  }

Häufige Ursachen:

  • Beabsichtigte Änderungen (mit --update-snapshots aktualisieren)
  • Unbeabsichtigte Änderungen (Code reparieren)
  • Dynamische Daten (Property-Matcher verwenden)
  • Umgebungsunterschiede (Daten normalisieren)

Plattformunterschiede

Achten Sie auf plattformspezifische Unterschiede:

test.ts
ts
// 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:

example.test.ts.snap
ts
// 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

bash
# Snapshot-Dateigrößen überprüfen
ls -la __snapshots__/

# Große Snapshots identifizieren
find __snapshots__ -name "*.snap" -exec wc -l {} \; | sort -n

Migration von Jest

Buns Snapshot-API ist mit Jest kompatibel, sodass die Migration einfach ist:

ts
// 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.

Bun von www.bunjs.com.cn bearbeitet