Zum Inhalt springen
aviral gupta

// I4.4 · ca. 36 Min. · Aufbau

Testen mit node:test und assert

Nach dieser Lektion schreiben Sie Tests für Ihre eigenen Funktionen mit dem, was Node.js schon mitbringt, führen sie mit einem Befehl aus und lesen einen fehlgeschlagenen Bericht, ohne zu raten.

Lektion 4 von 5 in I4 Fehler, Debugging und Tests

Danach können Sie

  • Tests mit test() aus node:test schreiben und mit node --test ausführen
  • Die passende Assertion wählen: equal, deepEqual, throws, rejects oder match, mit einer Meldung
  • Den Testbericht lesen: welcher Test scheiterte, Ist- gegen Sollwert und der Exit-Code
  1. Aufwärmen · Aufgabe 1 von 7

    Zum Aufwärmen, aus Lektion I4.1: eine Funktion, die in try wirft. Was gibt dieser Code aus?

    try {
      JSON.parse("{");
      console.log("parsed");
    } catch (error) {
      console.log("caught", error.name);
    }
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen: Beide Objekte sehen gleich aus. Was gibt dieser Code aus?

    import assert from "node:assert/strict";
    
    try {
      assert.equal({qty: 2}, {qty: 2});
      console.log("same");
    } catch (error) {
      console.log(error.message.split("\n")[0]);
    }
  3. Üben · Aufgabe 3 von 7

    Ergänzen Sie den Namen der Funktion, die einen Test definiert.

    import {____} from "node:test";
    import {} from "node:test";
  4. Üben · Aufgabe 4 von 7

    Diese Datei wird mit node --test ausgeführt. Was sagt die Zusammenfassung?

    import {test} from "node:test";
    import assert from "node:assert/strict";
    
    test("adds", () => {
      assert.equal(1 + 1, 2);
    });
    
    test("adds cents", () => {
      assert.equal(0.1 + 0.2, 0.3);
    });
    
    test("lists", () => {
      assert.deepEqual([1, 2].map((n) => n * 2), [2, 4]);
    });
  5. Üben · Aufgabe 5 von 7

    Welche Assertion besteht bei assert.____([5, 0.3], [5, 0.3])?

  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. loadUser(1) wird erfüllt, die Assertion sollte also scheitern. Die Datei heißt cart.test.js. Welche Zeilen von node --test sind mit ✖ markiert?

    import {test} from "node:test";
    import assert from "node:assert/strict";
    
    async function loadUser(id) {
      if (id < 1) throw new RangeError("id must be positive");
      return {id, name: "Ada"};
    }
    
    test("a bad id is rejected", () => {
      assert.rejects(loadUser(1), RangeError);
    });
  7. Anwenden · Aufgabe 7 von 7

    Kleine Aufgabe: Schreiben Sie in cart.test.js vier Tests für parseQty(text), das eine ganze Zahl ab 1 zurückgibt und sonst einen RangeError mit der Meldung Not a quantity: <text> wirft. Testen Sie einen gültigen Wert mit equal, eine Liste von Werten mit deepEqual, den RangeError für "0" mit throws und die Meldung für "abc" mit throws und einem regulären Ausdruck. Führen Sie node --test aus, bis es 4 bestanden meldet.

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.

Ausgearbeitetes Beispiel

Jede Art von Assertion, bestanden und gescheitert

Statt des Test-Runners führt eine kleine Funktion check() jede Prüfung in try...catch aus und gibt die erste Zeile des Fehlers aus; so entscheidet auch node --test, nur ohne Zeitangaben, die Ausgabe bleibt also bei jedem Lauf gleich. Zwei Prüfungen scheitern absichtlich: equal bei zwei Arrays und eine falsche Erwartung.

main.js

import assert from "node:assert/strict";

function lineTotal(item) {
  if (!Number.isInteger(item.qty) || item.qty < 1) {
    throw new RangeError("Quantity must be at least 1");
  }
  return Math.round(item.price * item.qty * 100) / 100;
}

// test() from node:test does the same: run the function, fail if it throws.
let failed = 0;
function check(name, fn) {
  try {
    fn();
    console.log("✔", name);
  } catch (error) {
    failed++;
    console.log("✖", name);
    console.log("  " + error.message.split("\n")[0]);
  }
}

check("2 x 2.50 is 5", () => assert.equal(lineTotal({price: 2.5, qty: 2}), 5));
check("3 x 0.10 is rounded to 0.3", () => assert.equal(lineTotal({price: 0.1, qty: 3}), 0.3));
check("a cart maps to its totals", () => {
  const cart = [{price: 2.5, qty: 2}, {price: 0.1, qty: 3}];
  assert.deepEqual(cart.map(lineTotal), [5, 0.3]);
});
check("a quantity of 0 is refused", () => {
  assert.throws(() => lineTotal({price: 1, qty: 0}), RangeError);
});
check("the message names the rule", () => {
  assert.throws(() => lineTotal({price: 1, qty: 0}), /at least 1/);
});
check("arrays compared with equal", () => assert.equal([5, 0.3], [5, 0.3]));
check("a wrong expectation", () => {
  assert.equal(lineTotal({price: 1.2, qty: 2}), 2.5, "1.2 x 2 should be 2.40");
});

console.log(`${failed} of 7 failed`);

Ausführen mit

node main.js

Ausgabe

✔ 2 x 2.50 is 5
✔ 3 x 0.10 is rounded to 0.3
✔ a cart maps to its totals
✔ a quantity of 0 is refused
✔ the message names the rule
✖ arrays compared with equal
  Values have same structure but are not reference-equal:
✖ a wrong expectation
  1.2 x 2 should be 2.40
2 of 7 failed
  • equal passt für Zahlen und Strings; für Arrays und Objekte vergleicht deepEqual den Inhalt.
  • throws nimmt eine Funktion zum Aufrufen, dann eine Klasse oder einen regulären Ausdruck, auf die der Fehler passen muss.
  • Ein Meldungsargument ersetzt den Standardtext von assert, der Bericht sagt also, was gemeint war.

Übungen

Übung 1 von 3

Assertions mit Meldung

checkOrder(order) sichert den Beginn eines Checkouts mit assert ab: Eine Bestellung braucht mindestens einen Artikel, und jede Menge muss eine ganze Zahl ab 1 sein. Scheitert eine Prüfung, soll der AssertionError sagen, welche Regel verletzt wurde: Eine Bestellung braucht mindestens einen Artikel, oder jede Menge muss mindestens 1 sein. Der Starter prüft das Richtige, aber seine Meldungen sind der Standardtext von assert.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den JavaScript-Runner herunter (bis zu 0.1 MB) und speichert ihn im Cache. Ihr Code läuft in der eigenen Engine Ihres Browsers und bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Führen Sie die Tests aus: Welche Meldung hat der AssertionError jetzt?

  2. Hinweis 2

    Jede assert-Funktion nimmt als letztes Argument eine Meldung.

  3. Hinweis 3

    assert.ok(order.items.length > 0, "an order needs at least one item")

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

import assert from "node:assert/strict";

export function checkOrder(order) {
  assert.ok(order.items.length > 0, "an order needs at least one item");
  for (const item of order.items) {
    assert.ok(Number.isInteger(item.qty) && item.qty >= 1, "every quantity must be at least 1");
  }
  return true;
}
Auf dem eigenen Computer ausführen

Installieren Sie ECMAScript 2026 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.js

import assert from "node:assert/strict";

export function checkOrder(order) {
  assert.ok(order.items.length > 0);
  for (const item of order.items) {
    assert.ok(Number.isInteger(item.qty) && item.qty >= 1);
  }
  return true;
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {checkOrder} from './main.js';

test('eine gute Bestellung besteht', () => {
  assert.equal(checkOrder({items: [{qty: 2}]}), true, 'checkOrder sollte true zurückgeben');
});

test('eine leere Bestellung nennt ihre Regel', () => {
  assert.throws(() => checkOrder({items: []}), {name: 'AssertionError', message: 'an order needs at least one item'});
});

test('eine Menge von 0 nennt ihre Regel', () => {
  assert.throws(() => checkOrder({items: [{qty: 0}]}), {name: 'AssertionError', message: 'every quantity must be at least 1'});
});

package.json

{
  "type": "module"
}

package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.

Programm ausführen:

node main.js

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

node --test
learnrun.js herunterladen

Übung 2 von 3

Nah genug für Geld

assertClose(actual, expected, tolerance = 1e-9) soll bestehen, wenn sich zwei Zahlen höchstens um tolerance unterscheiden, und sonst einen AssertionError mit der Meldung <actual> is not within <tolerance> of <expected> werfen. Der Starter nutzt assert.equal, deshalb scheitert assertClose(0.1 + 0.2, 0.3), obwohl der Unterschied winzig ist.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den JavaScript-Runner herunter (bis zu 0.1 MB) und speichert ihn im Cache. Ihr Code läuft in der eigenen Engine Ihres Browsers und bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Führen Sie die Tests aus: Welcher scheitert, und was zeigt der Diff als actual?

  2. Hinweis 2

    Vergleichen Sie die Größe des Unterschieds: Math.abs(actual - expected) <= tolerance.

  3. Hinweis 3

    assert.ok(condition, message) wirft einen AssertionError mit Ihrer Meldung.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

import assert from "node:assert/strict";

export function assertClose(actual, expected, tolerance = 1e-9) {
  assert.ok(
    Math.abs(actual - expected) <= tolerance,
    `${actual} is not within ${tolerance} of ${expected}`
  );
}
Auf dem eigenen Computer ausführen

Installieren Sie ECMAScript 2026 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.js

import assert from "node:assert/strict";

export function assertClose(actual, expected, tolerance = 1e-9) {
  assert.equal(actual, expected);
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {assertClose} from './main.js';

test('0.1 + 0.2 liegt nahe bei 0.3', () => {
  assertClose(0.1 + 0.2, 0.3);
});

test('0.5 liegt nicht nahe bei 0.3', () => {
  assert.throws(() => assertClose(0.5, 0.3), {name: 'AssertionError', message: '0.5 is not within 1e-9 of 0.3'});
});

test('eine größere Toleranz akzeptiert 2.44 für 2.4', () => {
  assertClose(2.44, 2.4, 0.05);
});

package.json

{
  "type": "module"
}

package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.

Programm ausführen:

node main.js

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

node --test
learnrun.js herunterladen

Übung 3 von 3

Beheben, was der Bericht zeigt

cartTotal(items) soll für jede Zeile Preis × Menge addieren und das Ergebnis auf Cent runden. Der Test für einen Warenkorb aus 1,10 × 3 und 0,20 × 1 scheitert: Lesen Sie in seinem Bericht die Zeile nach +, den Wert, den cartTotal wirklich zurückgab. Beheben Sie cartTotal, nicht den Test.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den JavaScript-Runner herunter (bis zu 0.1 MB) und speichert ihn im Cache. Ihr Code läuft in der eigenen Engine Ihres Browsers und bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Führen Sie die Tests aus und lesen Sie die Zeile nach + im Diff.

  2. Hinweis 2

    1.1 × 3 ist in Gleitkommazahlen 3.3000000000000003, die Summe ist also nicht genau 3.5.

  3. Hinweis 3

    Runden Sie einmal am Ende: Math.round(total * 100) / 100.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

export function cartTotal(items) {
  const total = items.reduce((sum, item) => sum + item.price * item.qty, 0);
  return Math.round(total * 100) / 100;
}
Auf dem eigenen Computer ausführen

Installieren Sie ECMAScript 2026 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.js

export function cartTotal(items) {
  return items.reduce((sum, item) => sum + item.price * item.qty, 0);
}

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {cartTotal} from './main.js';

test('ein leerer Warenkorb kostet 0', () => {
  assert.equal(cartTotal([]), 0, 'cartTotal([]) sollte 0 sein');
});

test('1.10 x 3 und 0.20 x 1 ergeben 3.50', () => {
  assert.equal(cartTotal([{price: 1.1, qty: 3}, {price: 0.2, qty: 1}]), 3.5);
});

package.json

{
  "type": "module"
}

package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.

Programm ausführen:

node main.js

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

node --test
learnrun.js herunterladen

Häufige Fehler

Objekte mit equal vergleichen

import {test} from "node:test";
import assert from "node:assert/strict";

test("the order is copied", () => {
  const order = {id: 7, items: ["tea"]};
  assert.equal(structuredClone(order), order);
});

Was Node.js ausgibt

AssertionError [ERR_ASSERTION]: Values have same structure but are not reference-equal:

Warum, und die Lösung

equal vergleicht mit Object.is, und eine Kopie ist ein anderes Objekt, es scheitert also, obwohl alles darin gleich ist. Um den Inhalt von Arrays und Objekten zu vergleichen, nehmen Sie assert.deepEqual(structuredClone(order), order).

Die Funktion aufrufen, statt sie an throws zu übergeben

import {test} from "node:test";
import assert from "node:assert/strict";

function parseQty(text) {
  const qty = Number(text);
  if (!Number.isInteger(qty) || qty < 1) throw new RangeError("Quantity must be at least 1");
  return qty;
}

test("0 is refused", () => {
  assert.throws(parseQty("0"), RangeError);
});

Was Node.js ausgibt

RangeError: Quantity must be at least 1

Warum, und die Lösung

parseQty("0") läuft, bevor assert.throws überhaupt aufgerufen wird, der RangeError entkommt also und lässt den Test scheitern. assert.throws braucht eine Funktion, die es aufrufen und beobachten kann: assert.throws(() => parseQty("0"), RangeError).

assert.rejects nicht abwarten

import {test} from "node:test";
import assert from "node:assert/strict";

async function loadUser(id) {
  if (id < 1) throw new RangeError("id must be positive");
  return {id, name: "Ada"};
}

test("a bad id is rejected", () => {
  assert.rejects(loadUser(1), RangeError);
});

Was Node.js ausgibt

AssertionError [ERR_ASSERTION]: Missing expected rejection (RangeError).

Warum, und die Lösung

assert.rejects gibt ein Promise zurück. Ohne await kehrt der Test sofort zurück und gilt als bestanden; die gescheiterte Prüfung kommt erst nach dem Ende des Tests, als Hinweis auf asynchrone Aktivität, und node --test markiert die ganze Datei als gescheitert. Machen Sie den Test async und schreiben Sie await assert.rejects(loadUser(1), RangeError).

JavaScript im Browser: die eigene Engine Ihres Browsers in einem abgeschotteten Worker. Syntaxfehler findet acorn 8.18.0, MIT. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

Ein Test ist eine Funktion, die nicht werfen darf

import {test} from "node:test" und schreiben Sie test("name", () => { … }). Ein Test besteht, wenn seine Funktion zurückkehrt, ohne zu werfen, und scheitert, wenn sie wirft, ob der Fehler aus einer Assertion kommt oder aus einem Fehler im Code wie einem TypeError. Ein async-Test scheitert, wenn sein Promise abgelehnt wird. node --test findet Dateien wie cart.test.js im Ordner und seinen Unterordnern, führt sie aus und gibt pro Test eine Zeile aus.

Eine Assertion für jede Art von Prüfung

Aus node:assert/strict: equal(actual, expected) vergleicht mit Object.is und passt daher für Zahlen und Strings, aber zwei Objekte sind nur gleich, wenn es dasselbe Objekt ist. deepEqual vergleicht Inhalte, für Arrays und Objekte. throws(() => fn(), RangeError) erwartet, dass eine Funktion wirft; await assert.rejects(promise, TypeError) tut dasselbe für ein Promise. match(text, /regex/) prüft einen String. Jede Assertion nimmt als letztes Argument eine Meldung, die den Standardtext ersetzt.

Den Bericht lesen

Jeder Test bekommt eine Zeile: ✔ für bestanden, ✖ für gescheitert. Die Zusammenfassung zählt tests, pass und fail. Unter "failing tests" zeigt jeder Fehlschlag Datei und Zeile des Tests, den Fehler und bei equal und deepEqual einen Diff: + ist der tatsächliche Wert, den Ihr Code lieferte, - ist der erwartete Wert. Scheitert ein Test, ist der Exit-Code 1, und genau dadurch schlägt ein CI-Job fehl.

Quellen

Zuletzt geprüft am 5. Oktober 2026