Zum Hauptinhalt springen
Version: Nächste

Snapshot-Tests

Inoffizielle Beta-Übersetzung

Diese Seite wurde von PageTurner AI übersetzt (Beta). Nicht offiziell vom Projekt unterstützt. Fehler gefunden? Problem melden →

Snapshot-Tests sind ein äußerst nützliches Werkzeug, wenn Sie sicherstellen möchten, dass sich Ihre Benutzeroberfläche nicht unerwartet ändert.

Ein typischer Snapshot-Test rendert eine UI-Komponente, erstellt einen Snapshot und vergleicht ihn mit einer Referenz-Snapshot-Datei, die neben dem Test gespeichert ist. Der Test schlägt fehl, wenn die beiden Snapshots nicht übereinstimmen: Entweder ist die Änderung unerwartet, oder der Referenz-Snapshot muss an die neue Version der UI-Komponente angepasst werden.

Snapshot-Tests mit Jest

Ein ähnlicher Ansatz lässt sich beim Testen deiner React-Komponenten verfolgen. Anstatt die grafische Benutzeroberfläche zu rendern, was den Aufbau der gesamten App erfordern würde, kannst du einen Test-Renderer verwenden, um schnell einen serialisierbaren Wert für deine React-Komponente zu generieren. Sieh dir diesen Beispieltest für eine Link-Komponente an:

import {render} from '@testing-library/react';
import Link from '../Link';

it('renders correctly', () => {
const {container} = render(
<Link page="http://www.facebook.com">Facebook</Link>,
);
expect(container.firstChild).toMatchSnapshot();
});

Beim ersten Ausführen dieses Tests erstellt Jest eine Snapshot-Datei, die so aussieht:

exports[`renders correctly 1`] = `
<a
aria-label="normal
class="normal"
href="http://www.facebook.com"
>
Facebook
</a>
`;

Das Snapshot-Artefakt sollte zusammen mit Codeänderungen committet und als Teil Ihres Code-Review-Prozesses überprüft werden. Jest verwendet pretty-format, um Snapshots während des Code-Reviews lesbar zu gestalten. Bei nachfolgenden Testläufen vergleicht Jest die gerenderte Ausgabe mit dem vorherigen Snapshot. Bei Übereinstimmung besteht der Test. Wenn nicht, hat der Testrunner entweder einen Fehler in Ihrem Code (in diesem Fall der <Link>-Komponente) gefunden, der behoben werden sollte, oder die Implementierung hat sich geändert und der Snapshot muss aktualisiert werden.

Hinweis

Der Snapshot bezieht sich direkt auf die von Ihnen gerenderten Daten – in unserem Beispiel die <Link>-Komponente mit der übergebenen page-Prop. Das bedeutet, dass der Test selbst dann besteht, wenn in einer anderen Datei (z.B. App.js) Props in der <Link>-Komponente fehlen, da der Test nichts über die Verwendung der <Link>-Komponente weiß und nur auf Link.js beschränkt ist. Auch das Rendern derselben Komponente mit unterschiedlichen Props in anderen Snapshot-Tests beeinflusst den ersten Test nicht, da die Tests voneinander unabhängig sind.

Hinweis

Weitere Informationen zur Funktionsweise von Snapshot-Tests und warum wir sie entwickelt haben, finden Sie im Release-Blogbeitrag. Wir empfehlen, diesen Blogbeitrag zu lesen, um ein besseres Verständnis für sinnvolle Einsatzbereiche zu erhalten. Sehen Sie sich auch dieses egghead-Video zu Snapshot-Tests mit Jest an.

Snapshots aktualisieren

Es ist leicht zu erkennen, wenn ein Snapshot-Test aufgrund eines eingeführten Fehlers fehlschlägt. In diesem Fall beheben Sie das Problem und stellen Sie sicher, dass Ihre Snapshot-Tests wieder bestehen. Nun betrachten wir den Fall, wenn ein Snapshot-Test aufgrund einer beabsichtigten Implementierungsänderung fehlschlägt.

Eine solche Situation kann auftreten, wenn wir absichtlich die Adresse ändern, auf die die Link-Komponente in unserem Beispiel verweist.

// Updated test case with a Link to a different address
it('renders correctly', () => {
const {container} = render(
<Link page="http://www.instagram.com">Instagram</Link>,
);
expect(container.firstChild).toMatchSnapshot();
});

In diesem Fall gibt Jest folgende Ausgabe aus:

Da wir unsere Komponente gerade aktualisiert haben, um auf eine andere Adresse zu verweisen, sind Änderungen im Snapshot für diese Komponente zu erwarten. Unser Snapshot-Test schlägt fehl, weil der Snapshot für unsere aktualisierte Komponente nicht mehr mit dem Snapshot-Artefakt für diesen Testfall übereinstimmt.

Um dies zu beheben, müssen wir unsere Snapshot-Artefakte aktualisieren. Sie können Jest mit einem Flag ausführen, das ihm anweist, Snapshots neu zu generieren:

jest --updateSnapshot

Führe den obigen Befehl aus, um die Änderungen zu akzeptieren. Du kannst auch das gleichwertige Kurzflag -u verwenden, um Snapshots neu zu generieren. Dadurch werden Snapshot-Artefakte für alle fehlgeschlagenen Snapshot-Tests neu erstellt. Falls zusätzliche fehlgeschlagene Tests aufgrund unbeabsichtigter Fehler existieren, müssen diese zuerst behoben werden, bevor Snapshots neu generiert werden, um das Erfassen fehlerhafter Verhaltensweisen zu vermeiden.

Wenn du einschränken möchtest, welche Snapshot-Testfälle neu generiert werden, kannst du das zusätzliche Flag --testNamePattern übergeben, um nur Snapshots für Tests neu aufzunehmen, die dem angegebenen Muster entsprechen.

Du kannst diese Funktion testen, indem du das Snapshot-Beispiel klonst, die Link-Komponente anpasst und Jest ausführst.

Interaktiver Snapshot-Modus

Fehlgeschlagene Snapshots können auch im Watch-Modus interaktiv aktualisiert werden:

Sobald du den interaktiven Snapshot-Modus startest, führt dich Jest schrittweise durch fehlgeschlagene Snapshots und ermöglicht dir, die fehlerhafte Ausgabe zu überprüfen.

Hier kannst du entscheiden, ob du diesen Snapshot aktualisieren oder zum nächsten springen möchtest:

Nach Abschluss zeigt dir Jest eine Zusammenfassung an, bevor es in den Watch-Modus zurückkehrt:

Inline-Snapshots

Inline-Snapshots verhalten sich identisch zu externen Snapshots (.snap-Dateien), nur dass die Snapshot-Werte automatisch in den Quellcode zurückgeschrieben werden. So profitierst du von automatisch generierten Snapshots, ohne eine externe Datei prüfen zu müssen.

Beispiel:

Zuerst schreibst du einen Test, der .toMatchInlineSnapshot() ohne Argumente aufruft:

it('renders correctly', () => {
const {container} = render(
<Link page="https://example.com">Example Site</Link>,
);
expect(container.firstChild).toMatchInlineSnapshot();
});

Beim nächsten Jest-Lauf wird tree ausgewertet und ein Snapshot als Argument an toMatchInlineSnapshot angehängt:

it('renders correctly', () => {
const {container} = render(
<Link page="https://example.com">Example Site</Link>,
);
expect(container.firstChild).toMatchInlineSnapshot(`
<a
aria-label="normal"
className="normal"
href="https://example.com"
>
Example Site
</a>
`);
});

Das war's! Du kannst Snapshots sogar mit --updateSnapshot oder durch Drücken der u-Taste im --watch-Modus aktualisieren.

Standardmäßig schreibt Jest Snapshots selbst in deinen Quellcode. Bei Verwendung von Prettier erkennt Jest dies jedoch und delegiert die Arbeit an Prettier (inklusive Berücksichtigung deiner Konfiguration).

Eigenschafts-Matcher

Oft enthalten zu snapshottende Objekte dynamisch generierte Felder (wie IDs oder Datumsangaben). Beim Versuch, diese zu erfassen, würde der Snapshot bei jedem Durchlauf scheitern:

it('will fail every time', () => {
const user = {
createdAt: new Date(),
id: Math.floor(Math.random() * 20),
name: 'LeBron James',
};

expect(user).toMatchSnapshot();
});

// Snapshot
exports[`will fail every time 1`] = `
{
"createdAt": 2018-05-19T23:36:09.816Z,
"id": 3,
"name": "LeBron James",
}
`;

Für solche Fälle erlaubt Jest asymmetrische Matcher für beliebige Eigenschaften. Diese werden vor dem Schreiben oder Testen des Snapshots überprüft und stattdessen in der Snapshot-Datei gespeichert:

it('will check the matchers and pass', () => {
const user = {
createdAt: new Date(),
id: Math.floor(Math.random() * 20),
name: 'LeBron James',
};

expect(user).toMatchSnapshot({
createdAt: expect.any(Date),
id: expect.any(Number),
});
});

// Snapshot
exports[`will check the matchers and pass 1`] = `
{
"createdAt": Any<Date>,
"id": Any<Number>,
"name": "LeBron James",
}
`;

Alle Werte, die keine Matcher sind, werden exakt geprüft und im Snapshot gespeichert:

it('will check the values and pass', () => {
const user = {
createdAt: new Date(),
name: 'Bond... James Bond',
};

expect(user).toMatchSnapshot({
createdAt: expect.any(Date),
name: 'Bond... James Bond',
});
});

// Snapshot
exports[`will check the values and pass 1`] = `
{
"createdAt": Any<Date>,
"name": 'Bond... James Bond',
}
`;
Tipp

Bei Strings (nicht Objekten) musst du zufällige Teile selbst ersetzen, bevor du den Snapshot testest.
Verwende dazu z.B. replace() mit regulären Ausdrücken.

const randomNumber = Math.round(Math.random() * 100);
const stringWithRandomData = `<div id="${randomNumber}">Lorem ipsum</div>`;
const stringWithConstantData = stringWithRandomData.replace(/id="\d+"/, 123);
expect(stringWithConstantData).toMatchSnapshot();

Alternativen sind der Einsatz von Snapshot-Serializern oder das Mocken der Bibliothek, die den zufälligen Codeanteil generiert.

Best Practices

Schnappschüsse sind ein fantastisches Werkzeug, um unerwartete Änderungen an Schnittstellen in Ihrer Anwendung zu erkennen – sei es eine API-Antwort, UI, Protokolle oder Fehlermeldungen. Wie bei jeder Teststrategie gibt es einige Best Practices, die Sie kennen sollten, und Richtlinien, die Sie befolgen sollten, um sie effektiv zu nutzen.

1. Behandeln Sie Schnappschüsse wie Code

Führen Sie Schnappschüsse in Ihre Versionskontrolle ein und überprüfen Sie sie als Teil Ihres regulären Code-Review-Prozesses. Das bedeutet, Schnappschüsse wie jeden anderen Test oder Code in Ihrem Projekt zu behandeln.

Stellen Sie sicher, dass Ihre Schnappschüsse lesbar bleiben, indem Sie sie fokussiert und kurz halten und Tools verwenden, die diese stilistischen Konventionen durchsetzen.

Wie bereits erwähnt, verwendet Jest pretty-format, um Schnappschüsse menschenlesbar zu gestalten. Zusätzliche Tools wie eslint-plugin-jest mit der no-large-snapshots-Option oder snapshot-diff mit seiner Komponenten-Snapshot-Vergleichsfunktion können helfen, kurze, fokussierte Assertions zu fördern.

Ziel ist es, die Überprüfung von Schnappschüssen in Pull Requests zu erleichtern und der Gewohnheit entgegenzuwirken, Schnappschüsse bei Testfehlern einfach neu zu generieren, statt die Ursachen zu untersuchen.

2. Tests sollten deterministisch sein

Ihre Tests sollten deterministisch sein. Das mehrfache Ausführen derselben Tests auf einer unveränderten Komponente sollte jedes Mal identische Ergebnisse liefern. Sie sind dafür verantwortlich, dass Ihre generierten Schnappschüsse keine plattformspezifischen oder anderen nicht-deterministischen Daten enthalten.

Wenn Sie beispielsweise eine Clock-Komponente mit Date.now() haben, unterscheidet sich der generierte Schnappschuss bei jedem Testlauf. Hier können wir die Date.now()-Methode mocken, um bei jedem Testlauf einen konsistenten Wert zurückzugeben:

Date.now = jest.fn(() => 1_482_363_367_071);

Nun gibt Date.now() bei jedem Schnappschuss-Testlauf konsistent 1482363367071 zurück. Dadurch wird unabhängig vom Testzeitpunkt derselbe Schnappschuss für diese Komponente generiert.

3. Verwenden Sie beschreibende Snapshot-Namen

Streben Sie stets nach aussagekräftigen Test- und/oder Snapshot-Namen. Ideale Namen beschreiben den erwarteten Snapshot-Inhalt. Dies erleichtert Reviewern die Überprüfung während des Code-Reviews und hilft allen Beteiligten zu erkennen, ob ein veralteter Snapshot das korrekte Verhalten darstellt.

Vergleichen Sie beispielsweise:

exports[`<UserName /> should handle some test case`] = `null`;

exports[`<UserName /> should handle some other test case`] = `
<div>
Alan Turing
</div>
`;

Mit:

exports[`<UserName /> should render null`] = `null`;

exports[`<UserName /> should render Alan Turing`] = `
<div>
Alan Turing
</div>
`;

Da Letzteres genau beschreibt, was in der Ausgabe erwartet wird, sind Abweichungen klarer erkennbar:

exports[`<UserName /> should render null`] = `
<div>
Alan Turing
</div>
`;

exports[`<UserName /> should render Alan Turing`] = `null`;

Häufig gestellte Fragen

Werden Snapshots automatisch auf CI-Systemen erstellt?

Nein, ab Jest 20 werden Snapshots nicht automatisch erstellt, wenn Jest in einer CI-Umgebung ohne explizites --updateSnapshot läuft. Alle Snapshots sollten Teil des auf CI ausgeführten Codes sein. Da neue Snapshots automatisch bestehen, sollten sie keinen CI-Testlauf passieren. Es wird empfohlen, alle Snapshots stets einzuchecken und in der Versionskontrolle zu halten.

Sollten Snapshot-Dateien eingecheckt werden?

Ja, alle Snapshot-Dateien sollten zusammen mit den zugehörigen Modulen und Tests eingecheckt werden. Sie sind wie jede andere Assertion in Jest zu betrachten. Tatsächlich repräsentieren Snapshots den Zustand der Quellmodule zu einem bestimmten Zeitpunkt. So kann Jest bei Modifikationen erkennen, was sich geändert hat. Sie bieten zusätzlichen Kontext während Code-Reviews, wo Reviewer Änderungen besser nachvollziehen können.

Funktioniert Snapshot-Testing nur mit React-Komponenten?

React- und React Native-Komponenten sind ein typischer Anwendungsfall für Snapshot-Tests. Allerdings können Snapshots jeden serialisierbaren Wert erfassen und sollten immer dann verwendet werden, wenn das Ziel ist, die Korrektheit der Ausgabe zu überprüfen. Das Jest-Repository enthält zahlreiche Beispiele für das Testen der Ausgabe von Jest selbst, der Assertion-Bibliothek von Jest sowie von Protokollnachrichten aus verschiedenen Teilen der Jest-Codebasis. Ein Beispiel für das Erfassen von CLI-Ausgabe-Snapshots finden Sie im Jest-Repo.

Was ist der Unterschied zwischen Snapshot-Tests und visuellen Regressionstests?

Snapshot-Tests und visuelle Regressionstests sind zwei unterschiedliche Methoden zum Testen von Benutzeroberflächen mit verschiedenen Zielsetzungen. Visuelle Regressionstest-Tools erstellen Screenshots von Webseiten und vergleichen die resultierenden Bilder Pixel für Pixel. Bei Snapshot-Tests werden Werte serialisiert, in Textdateien gespeichert und mit einem Diff-Algorithmus verglichen. Es gibt verschiedene Vor- und Nachteile zu beachten, und wir haben die Gründe für die Entwicklung von Snapshot-Tests im Jest-Blog aufgeführt.

Ersetzt Snapshot-Testing das Unit-Testing?

Snapshot-Testing ist nur eine von über 20 Assertions, die mit Jest ausgeliefert werden. Ziel ist nicht, bestehende Unit-Tests zu ersetzen, sondern zusätzlichen Nutzen zu bieten und Tests schmerzfrei zu gestalten. In manchen Szenarien kann Snapshot-Testing den Bedarf an Unit-Tests für bestimmte Funktionsbereiche (z.B. React-Komponenten) reduzieren, aber beide Ansätze können auch kombiniert werden.

Wie performant sind Snapshot-Tests bezüglich Geschwindigkeit und Dateigröße?

Jest wurde mit Fokus auf Performance entwickelt, und Snapshot-Tests bilden keine Ausnahme. Da Snapshots in Textdateien gespeichert werden, ist diese Testmethode schnell und zuverlässig. Jest generiert für jede Testdatei, die den toMatchSnapshot-Matcher aufruft, eine separate Datei. Die Snapshots sind sehr kompakt: Zum Vergleich sind alle Snapshot-Dateien der Jest-Codebasis selbst kleiner als 300 KB.

Wie löse ich Konflikte in Snapshot-Dateien?

Snapshot-Dateien müssen stets den aktuellen Zustand der zugehörigen Module widerspiegeln. Bei Merge-Konflikten zwischen Branches können Sie Konflikte entweder manuell lösen oder die Snapshot-Datei durch Ausführen von Jest aktualisieren und das Ergebnis überprüfen.

Lassen sich Test-Driven-Development-Prinzipien mit Snapshot-Tests anwenden?

Obwohl das manuelle Erstellen von Snapshot-Dateien möglich ist, ist dieser Ansatz meist nicht praktikabel. Snapshots helfen primär dabei, Änderungen in der Ausgabe getesteter Module zu erkennen, anstatt als Entwurfshilfe für den Code zu dienen.

Funktioniert Code Coverage mit Snapshot-Tests?

Ja, genauso wie mit jedem anderen Test.