Die Testumgebung

Wie bereits unter Was sind Tests? erläutert, handelt es sich bei JavaScript-Tests im Wesentlichen nur um Code, den wir überprüfen können, ohne dass ein Error ausgelöst wird. Eine der Möglichkeiten dieser Definition ist jedoch, dass sie nicht berücksichtigt, wo der Code ausgeführt wird (die Testumgebung).

Die Testumgebung kann man sich grob als zwei Komponenten vorstellen: die Laufzeitumgebung, in der Sie den Test ausführen (z. B. Node oder der Browser), und die APIs, die Ihnen zur Verfügung stehen.

Die Laufzeitumgebung

Laufzeiten wie Node oder ähnliche Tools wie Deno oder Bun unterstützen serverseitigen oder allgemeinen JS-Code. Ihre Umgebungen enthalten weder APIs, die Sie möglicherweise in einem Browser erwarten würden, z. B. das Erstellen und Arbeiten mit den DOM- und HTML-Elementen, noch ein Konzept einer visuellen Komponente oder eines Renderingziels, d. h. nicht nur Elemente, sondern das visuelle Rendern dieser Elemente mit CSS im Darstellungsbereich.

Daher schlagen diese allgemeinen Laufzeiten fehl, wenn Sie beispielsweise versuchen, React-Elemente zu rendern, damit sie getestet werden können, da keine document- oder window-Objekte verfügbar sind.

Wenn Sie Ihre Tests dagegen in einem Browser ausführen, sind integrierte APIs, die Sie von diesen Laufzeiten erwarten können, möglicherweise nicht ohne Polyfilling oder zusätzliche Arbeit verfügbar. Ein häufiges Problem ist wie das Lesen und Schreiben von Dateien: Es ist nicht möglich, in einem Browser import { fs } from 'node:'fs'; zu aktivieren und eine Datei auf diese Weise im Rahmen eines Tests zu lesen.

Dieses API-Problem vom Typ „Web“ im Vergleich zu einem „Back-End“ geht etwas über den reinen Testumfang hinaus, da es unangenehm sein kann, eine Codebasis sowohl mit Server- als auch mit Clientteilen zu haben. Es basiert jedoch auf der Idee des Schreibens von testbarem Code, auf die wir in diesem Kurs noch einmal eingehen werden.

Algorithmus- oder Geschäftslogik testen

Für einen Teil Ihres Codes sind weder Knoten- noch Browserimporte erforderlich, sodass Tests durchgeführt werden können. Darauf kommen wir später in diesem Kurs noch zu sprechen. Wenn Sie Ihre Codebasis jedoch so strukturieren, dass ihre reine „Geschäftslogik“ vom Rendering oder knotenspezifischen Code getrennt ist, lässt sich das Testen vereinfachen.

Für ein kurzes Beispiel könnten Sie eine Node-Funktion haben, die eine Datei vom Laufwerk liest und schreibt und sie während des Vorgangs ändert. Da Sie die Funktion so refaktoriert haben, dass sie Funktionen akzeptiert, die das Lesen und Schreiben vom Laufwerk ausführen, haben Sie sie überall testbar gemacht.

In diesem Fall können Sie den Code in einer beliebigen Umgebung in einer serverseitigen Laufzeit oder im Browser testen. In Ihrem Test können Sie Hilfsfunktionen bereitstellen, die eine virtuelle Datei im Arbeitsspeicher speichern oder Platzhalterdaten zurückgeben. Diese Art von Hilfsprogramm kann gut in einen Test eingebunden werden, da es nicht wichtig ist, beispielsweise zu prüfen, ob fs.writeFileSync funktioniert. Konzentrieren Sie sich auf Ihren Code und darauf, was ihn einzigartig oder risikoreich macht.

Browser-APIs emulieren

Viele Test-Frameworks wie Vitest bieten die Möglichkeit, die API-Umgebung des Browsers zu emulieren, ohne einen Browser ausführen zu müssen. Vitest verwendet intern eine Bibliothek namens JSDOM. Dies kann eine gute Wahl für einfache Komponententests sein, bei denen der Aufwand für die Nutzung eines Browsers hoch ist.

Ein gemeinsames Merkmal von Emulationsbibliotheken ist, dass sie zwar einen Browser emulieren können, z. B. das DOM, die Elemente und Cookies, aber keine visuelle Komponente haben. Das bedeutet, dass sie eine zwingende Möglichkeit für die Arbeit mit HTML-Elementen und anderen Primitiven bieten. Es ist jedoch nicht möglich, die Ausgabe als Bild oder Bildschirm zu rendern oder die Position eines Elements in Pixeln auf der Seite zu prüfen.

Auch diese Option eignet sich gut für Komponententests, bei denen eine Komponente ein React-Element, eine Webkomponente usw. darstellt. Bei diesen Komponenten wird das DOM in der Regel nur relativ gering erstellt und mit ihm interagiert. Ein emulierter Browser kann genügend Funktionen bieten, um zu prüfen, ob die Komponente wie gewünscht funktioniert. Im folgenden Abschnitt finden Sie ein Beispiel für einen React-Komponententest mit Vitest und JSDOM.

Das Emulieren eines Browsers ist eine bewährte Praxis – JSDOM wurde 2014 veröffentlicht –, unterscheidet sich jedoch immer von der Verwendung eines echten Browsers. Diese Unterschiede können offensichtlich sein: JSDOM enthält beispielsweise keine Layout-Engine, sodass es keine Möglichkeit gibt, die Größe eines Elements zu überprüfen oder eine komplexe Geste wie Wischen zu testen. Die Unterschiede können auch subtil und unbekannt sein. Deshalb ist es am besten, Ihre JSDOM-basierten Tests prägnant zu halten. So können Sie das Risiko einstufen, dass jedes Verhalten vom tatsächlichen Verhalten abweicht.

Echten Browser steuern

Wenn Sie den Code so testen möchten, wie Ihre Nutzer ihn verwenden werden, ist die Verwendung eines echten Browsers die beste Wahl. In der Praxis werden durch das Testen von Laufzeiten, die den Browser unterstützen, Instanzen eines echten Browsers gestartet und gesteuert, auch wenn sie in Node.js "start" ausführen.

Wenn Sie einen Browser im Rahmen eines Tests steuern, wird er wie für einen Nutzer geöffnet. Der Test kann also URLs, benutzerdefinierten HTML- und JS-Code oder andere Elemente laden, die zum Durchführen des Tests erforderlich sind. Anschließend können Sie Code schreiben, der als Nutzer fungiert und beispielsweise die Maus steuert oder eine Eingabe in Eingabefelder eingibt.

Moderne Tools wie WebdriverIO oder Web Test Runner können alle wichtigen Browser steuern und sogar mehrere Instanzen gleichzeitig ausführen. Diese Browser können neben dem Test-Runner ausgeführt werden (z. B. auf Ihrem eigenen Computer oder im Rahmen einer CI-Aktion) oder an externe kommerzielle Dienste ausgelagert werden, die sie für Sie ausführen.

Etablierte Testbibliotheken (einschließlich Vitest und Jest) haben oft einen Browsermodus. Da ihr Ursprung jedoch von Node.js stammt, sind die Browsermodi oft „verschoben“ und es fehlen nützliche Funktionen. Vitest kann beispielsweise keine Modulimporte im Browser simulieren, da es sich um eine leistungsstarke Primitive handelt, die wir im Beispiel auf der nächsten Seite verwenden.

In der Praxis

Mit zunehmender Komplexität Ihrer Tests wird die Verwendung eines echten Browsers immer wichtiger.

  • Bei Tests, bei denen keine oder nur minimale Funktionen des DOMs verwendet werden, selbst bei Funktionen, die in Node.js und ähnlichen Laufzeiten verfügbar sind, wie fetch oder EventTarget, spielt die Umgebung keine Rolle.
  • Für Tests mit kleinen Komponenten kann JSDOM geeignet sein.
  • Größere Tests, z. B. End-to-End-Tests, die die Anmeldung eines Nutzers und das Ausführen einer Hauptaktion simulieren können, sind sinnvoll, wenn sie vollständig in einem echten Browser ausgeführt werden sollen.

In diesem Abschnitt geht es um die Theorie und es werden unterschiedliche Sichtweisen aufgezeigt, wo Sie Ihre Tests durchführen sollten. In der Praxis verwendet Ihre Codebasis oft viele verschiedene Ansätze für verschiedene Arten von Tests, die Ihren Anforderungen und dem, was die Testtools bieten, entsprechen.

Wissen testen

Welche Browserfunktionen werden von der Emulationsschicht JSdom *nicht* unterstützt?

Layout-Engine
Da JSDOM kein visuelles Tool ist, kann es nicht verwendet werden, um die Position eines Elements auf der Seite, die aufgelösten CSS-Attribute oder andere Teile des Website-Layouts zu prüfen.
WebSocket
JSDOM enthält den WebSocket-Polyfill, daher funktioniert Code, der ihn verwendet.
requestAnimationFrame
Mit dem Flag „pretendToBeVisual“ ruft jsdom den Callback „animation“ mit 60 fps auf, obwohl in Wirklichkeit nichts gezeichnet wird.