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