Ich kann raten, was Sie denken: *Noch* eine React-Testing-Bibliothek? So viele wurden bereits hier auf CSS-Tricks behandelt (verdammt, ich habe bereits eine veröffentlicht über Jest und Enzyme), gibt es also nicht bereits genug Optionen?
Aber react-testing-library ist nicht nur eine weitere Testbibliothek. Es ist eine Testbibliothek, ja, aber eine, die auf einem grundlegenden Prinzip basiert, das sie von anderen unterscheidet.
Je mehr Ihre Tests dem ähneln, wie Ihre Software verwendet wird, desto mehr Vertrauen können sie Ihnen geben.
Sie versucht, Tests dafür zu adressieren, *wie* ein Benutzer Ihre Anwendung nutzen wird. Tatsächlich geschieht dies auf eine Weise, die verhindert, dass Tests selbst bei Refactoring von Komponenten fehlschlagen. Und ich weiß, dass das etwas ist, auf das wir alle irgendwann in unserer React-Reise gestoßen sind.
Wir werden nun einige Zeit damit verbringen, gemeinsam Tests mit react-testing-library für eine einfache To-Do-Anwendung zu schreiben, die ich erstellt habe. Sie können das Repository lokal klonen
git clone https://github.com/kinsomicrote/todoapp-test.git
Und wenn Sie das tun, installieren Sie als Nächstes die erforderlichen Pakete
## yarn
yarn add --dev react-testing-library jest-dom
## npm
npm install --save-dev react-testing-library jest-dom
Falls Sie sich fragen, warum Jest dabei ist: Wir verwenden es für die Bestätigung. Erstellen Sie einen Ordner namens __test__ im Verzeichnis src und eine neue Datei namens App.test.js.
Snapshots erstellen
Snapshot-Tests speichern eine Aufzeichnung der Tests, die an einer getesteten Komponente durchgeführt wurden, um visuell zu sehen, was sich zwischen Änderungen ändert.
Wenn wir diesen Test zum ersten Mal ausführen, erstellen wir den ersten Snapshot davon, wie die Komponente aussieht. Daher wird der erste Test mit Sicherheit bestehen, denn nun ja, es gibt keinen anderen Snapshot, mit dem er verglichen werden könnte, der etwas Fehlerhaftes anzeigen würde. Er schlägt nur fehl, wenn wir eine neue Änderung an der Komponente vornehmen, indem wir ein neues Element, eine Klasse, eine Komponente oder Text hinzufügen. Etwas hinzufügen, das nicht da war, als der Snapshot erstellt oder zuletzt aktualisiert wurde.
Der Snapshot-Test wird der erste sein, den wir hier schreiben werden. Öffnen wir die Datei App.test.js und gestalten wir sie wie folgt:
import React from 'react';
import { render, cleanup } from "react-testing-library";
import "jest-dom/extend-expect";
import App from './App';
afterEach(cleanup);
it("matches snapshot", () => {
const { asFragment } = render(<App />);
expect(asFragment()).toMatchSnapshot();
});
Dies importiert die notwendigen Pakete, die wir zum Schreiben und Ausführen der Tests verwenden. render wird verwendet, um die Komponente anzuzeigen, die wir testen möchten. Wir verwenden cleanup, um nach jedem Testlauf aufzuräumen – wie Sie an der Zeile afterEach(cleanup) sehen können.
Mit asFragment erhalten wir ein DocumentFragment der gerenderten Komponente. Dann erwarten wir, dass es mit dem erstellten Snapshot übereinstimmt.
Lassen Sie uns den Test ausführen, um zu sehen, was passiert.
## yarn
yarn test
## npm
npm test
Wie wir jetzt wissen, wird ein Snapshot der Komponente in einem neuen Ordner namens __snapshots__ innerhalb des Verzeichnisses __tests__ erstellt, wenn dies unser erster Test ist. Wir erhalten darin eine Datei namens App.test.js.snap, die wie folgt aussieht:
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`matches snapshot 1`] = `
<DocumentFragment>
<div
class="container"
>
<div
class="row"
>
<div
class="col-md-6"
>
<h2>
Add Todo
</h2>
</div>
</div>
<form>
<div
class="row"
>
<div
class="col-md-6"
>
<input
class="form-control"
data-testid="todo-input"
placeholder="Enter a task"
type="text"
value=""
/>
</div>
</div>
<div
class="row"
>
<div
class="col-md-6"
>
<button
class="btn btn-primary"
data-testid="add-task"
type="submit"
>
Add Task
</button>
</div>
</div>
</form>
<div
class="row todo-list"
>
<div
class="col-md-6"
>
<h3>
Lists
</h3>
<ul
data-testid="todos-ul"
>
<li>
<div>
Buy Milk
<button
class="btn btn-danger"
>
X
</button>
</div>
</li>
<li>
<div>
Write tutorial
<button
class="btn btn-danger"
>
X
</button>
</div>
</li>
</ul>
</div>
</div>
</div>
</DocumentFragment>
`;
Nun testen wir DOM-Elemente und Ereignisse
Unsere App enthält zwei To-Do-Elemente, die standardmäßig beim ersten Start der App angezeigt werden. Wir möchten sicherstellen, dass sie tatsächlich standardmäßig beim ersten App-Start angezeigt werden. Um dies zu testen, müssen wir die unsortierte Liste (<ul>) ansprechen und deren Länge überprüfen. Wir erwarten, dass die Länge gleich zwei ist – der Anzahl der Elemente.
it('it displays default todo items', () => {
const { getByTestId } = render(<App />);
const todoList = getByTestId('todos-ul');
expect(todoList.children.length).toBe(2);
});
In diesem Ausschnitt verwenden wir getByTestId, um die Test-IDs aus der App-Komponente zu extrahieren. Dann setzen wir todoList, um das Element todos-ul anzusprechen. Dies sollte zwei zurückgeben.
Nutzen Sie das bisher Gelernte, um einen Test zu schreiben, der bestätigt, dass ein Benutzer Werte in das Eingabefeld eingeben kann. Hier sind die Dinge, die Sie tun möchten:
- Holen Sie sich das Eingabefeld
- Setzen Sie einen Wert für das Eingabefeld
- Lösen Sie ein Änderungsereignis aus
- Bestätigen Sie, dass das Eingabefeld den Wert hat, den Sie ihm in Schritt 2 zugewiesen haben
Schauen Sie nicht auf meine Antwort unten! Nehmen Sie sich so viel Zeit, wie Sie brauchen.
Immer noch dabei? Großartig! Ich hole mir einen Kaffee und bin gleich zurück.
Mmm, Kaffee. ☕️
Oh, Sie sind fertig! Sie sind spitze. Vergleichen wir die Antworten. Meine sieht so aus:
it('allows input', () => {
const {getByTestId } = render(<App />)
let item = 'Learn React'
const todoInputElement = getByTestId('todo-input');
todoInputElement.value = item;
fireEvent.change(todoInputElement);
expect(todoInputElement.value).toBe('Learn React')
});
Mit getByTestId kann ich die Test-IDs in der Anwendung extrahieren. Dann erstelle ich eine Variable, die auf die Zeichenfolge Learn React gesetzt wird, und mache sie zum Wert des Eingabefeldes. Als Nächstes hole ich mir das Eingabefeld anhand seiner Test-ID und löse das Änderungsereignis aus, nachdem ich den Wert des Eingabefeldes gesetzt habe. Danach bestätige ich, dass der Wert des Eingabefeldes tatsächlich Learn React ist.
Passt das zu Ihrer Antwort? Hinterlassen Sie einen Kommentar, wenn Sie eine andere Methode haben!
Als Nächstes testen wir, ob wir ein neues To-Do-Element hinzufügen können. Wir müssen das Eingabefeld, den Button zum Hinzufügen neuer Elemente und die unsortierte Liste abrufen, da dies alle Elemente sind, die zum Erstellen eines neuen Elements benötigt werden.
Wir setzen einen Wert für das Eingabefeld und lösen dann einen Button-Klick aus, um die Aufgabe hinzuzufügen. Dies können wir tun, indem wir den Button mit getByText abrufen – durch Auslösen eines Klickereignisses auf dem DOM-Element mit dem Text Add Task sollten wir ein neues To-Do-Element hinzufügen können.
Lassen Sie uns bestätigen, dass die Anzahl der Kinder (Listenelemente) im unsortierten Listenelement gleich drei ist. Dies setzt voraus, dass die Standardaufgaben noch vorhanden sind.
it('adds a new todo item', () => {
const { getByText, getByTestId } = render(<App />);
const todoInputElement = getByTestId('todo-input');
const todoList = getByTestId('todos-ul');
todoInputElement.value = 'Learn React';
fireEvent.change(todoInputElement);
fireEvent.click(getByText('Add Task'))
expect(todoList.children.length).toBe(3);
});
Ziemlich gut, oder?
Dies ist nur eine Möglichkeit, in React zu testen
Sie können react-testing-library in Ihrer nächsten React-Anwendung ausprobieren. Die Dokumentation im Repository ist sehr ausführlich und – wie bei den meisten Tools – der beste Ausgangspunkt. Kent C. Dodds hat sie entwickelt und bietet einen vollständigen Kurs zum Testen bei Frontend Masters (Abonnement erforderlich), der auch die Besonderheiten von react-testing-library behandelt.
Das gesagt, dies ist nur eine Testressource für React. Es gibt natürlich auch andere, aber hoffentlich ist dies eine, die Sie jetzt ausprobieren möchten, nachdem Sie einen kleinen Einblick erhalten haben, aber nutzen Sie natürlich das, was für Ihr Projekt am besten geeignet ist.
„Tatsächlich geschieht dies auf eine Weise, die verhindert, dass Tests selbst bei Refactoring von Komponenten fehlschlagen.“
Nun… Ihre Beispiele widersprechen der These ein wenig, oder vielleicht haben Sie einen etwas anderen Ansatz für Unit-Tests dargestellt, als ich ihn kenne.
Listenbeispiel
Was, wenn ich aus irgendeinem Grund beschließe, div statt ul zu verwenden? Das ist keine gute Praxis, ich weiß, aber manchmal muss man das tun. Dann muss ich meine Tests refaktorisieren… Deshalb würde ich es vorziehen, die Listenkomponente in ListWrapper und ListItem aufzuteilen und nach der Existenz von ListItems zu suchen. Dadurch entkoppele ich Implementierungsdetails von den Geschäftsanforderungen.
Input-Beispiel
Ich mag es wirklich nicht, IDs oder Klassennamen in Unit-Tests zu verwenden. Hier ist der Grund: Wenn Sie wiederverwendbare Komponenten verwenden, besteht die Chance, dass Sie niemals eine ID verwenden sollten :) Wenn Sie Klassennamen als Referenz verwenden, besteht die Chance, dass Sie irgendwann die Klasse ändern und Ihren Test refaktorisieren müssen, nur weil Sie ‚big-red-button‘ in ‚big-orange-button‘ geändert haben. Auch hier ist es meiner Meinung nach besser, Komponentenreferenzen zu verwenden. Außerdem sehe ich nicht, wie dieser Code das Benutzerverhalten besser simuliert als
simulatevon Enzyme?Und Snapshot-Tests…
Ich habe nie einen Grund gefunden, sie zu schreiben. Ich habe Unit-Tests zum Testen der Logik oder Struktur von Komponenten. Ich habe Integrationstests zum Testen der Zusammenarbeit von Komponenten. Und schließlich habe ich (wenige) E2E-Tests zum Testen ganzer Seiten. Ich kann keinen Platz für Snapshot-Tests finden, da sie eindeutig keine visuellen Regressionstests sind (die wahrscheinlich nützlich sind, aber ich kenne sie nicht gut). Sie könnten zu vielen Fehlalarmen führen, vermute ich.
Was ist Ihre Meinung? Haben Sie sie in einem realen Szenario als nützlich empfunden?
Der Test, den wir gemacht haben, während Sie Kaffee geholt haben, setzt eigentlich nichts. Sie setzen
input.value = someValund testen danninput.value === input.value. Ich denke, der folgende Weg ist etwas klarer.Ich habe Fehler bekommen, als ich Ihren Anweisungen gefolgt bin. Anscheinend soll man heutzutage stattdessen @testing-library/react und @testing-library/jest installieren/importieren statt react-testing-library und jest-dom
import {render, cleanup, fireEvent} from ‘@testing-library/react’
import ‘@testing-library/jest-dom/extend-expect’