Zum Hauptinhalt springen
Version: Nächste

Asynchronen Code testen

Inoffizielle Beta-Übersetzung

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

In JavaScript ist es üblich, dass Code asynchron ausgeführt wird. Wenn asynchroner Code getestet werden soll, muss Jest wissen, wann dieser abgeschlossen ist, bevor es zum nächsten Test übergehen kann. Dafür bietet Jest mehrere Ansätze.

Promises

Gib ein Promise aus deinem Test zurück, und Jest wartet auf dessen Auflösung. Wenn das Promise abgelehnt wird, schlägt der Test fehl.

Angenommen, fetchData gibt ein Promise zurück, das den String 'peanut butter' liefern soll. Der Test könnte so aussehen:

test('the data is peanut butter', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});

Async/Await

Alternativ kannst du async und await in Tests verwenden. Für einen asynchronen Test setze das Schlüsselwort async vor die an test übergebene Funktion. Das gleiche fetchData-Szenario würde dann so getestet:

test('the data is peanut butter', async () => {
const data = await fetchData();
expect(data).toBe('peanut butter');
});

test('the fetch fails with an error', async () => {
expect.assertions(1);
try {
await fetchData();
} catch (error) {
expect(error).toMatch('error');
}
});

Du kannst async und await mit .resolves oder .rejects kombinieren.

test('the data is peanut butter', async () => {
await expect(fetchData()).resolves.toBe('peanut butter');
});

test('the fetch fails with an error', async () => {
await expect(fetchData()).rejects.toMatch('error');
});

Hier sind async und await lediglich syntaktischer Zucker für dieselbe Logik wie im Promises-Beispiel.

Vorsicht

Stelle sicher, dass du das Promise zurückgibst (oder mit await wartest) – ohne return/await wird dein Test abgeschlossen, bevor das von fetchData zurückgegebene Promise aufgelöst oder abgelehnt wird.

Bei erwarteter Ablehnung eines Promises verwende die .catch-Methode. Füge expect.assertions hinzu, um sicherzustellen, dass Assertions aufgerufen werden. Andernfalls würde ein erfülltes Promise nicht zum Testfehler führen.

test('the fetch fails with an error', () => {
expect.assertions(1);
return fetchData().catch(error => expect(error).toMatch('error'));
});

Callbacks

Ohne Promises kannst du Callbacks nutzen. Angenommen, fetchData erwartet einen Callback (ruft also Daten ab und ruft callback(null, data) nach Abschluss auf). Du möchtest testen, ob die gelieferten Daten dem String 'peanut butter' entsprechen.

Standardmäßig schließen Jest-Tests nach Ausführung des letzten Codes ab. Das bedeutet, dieser Test würde nicht wie beabsichtigt funktionieren:

// Don't do this!
test('the data is peanut butter', () => {
function callback(error, data) {
if (error) {
throw error;
}
expect(data).toBe('peanut butter');
}

fetchData(callback);
});

Das Problem: Der Test endet sofort nach Abschluss von fetchData, noch bevor der Callback aufgerufen wird.

Eine alternative test-Variante behebt dies: Verwende statt einer leeren Argumentliste ein Argument namens done. Jest wartet auf den Aufruf von done, bevor es den Test abschließt.

test('the data is peanut butter', done => {
function callback(error, data) {
if (error) {
done(error);
return;
}
try {
expect(data).toBe('peanut butter');
done();
} catch (error) {
done(error);
}
}

fetchData(callback);
});

Wenn done() nie aufgerufen wird, schlägt der Test fehl (mit Timeout-Fehler) – genau das erwünschte Verhalten.

Bei fehlgeschlagener expect-Assertion wird done() nicht aufgerufen. Um die Fehlerursache im Log zu sehen, musst du expect in einen try-Block packen und den Fehler im catch-Block an done übergeben. Sonst erhältst du nur einen unklaren Timeout-Fehler, der nicht anzeigt, welcher Wert von expect(data) empfangen wurde.

Vorsicht

Jest wirft einen Fehler, wenn eine Testfunktion sowohl done() als auch ein Promise zurückgibt. Dies verhindert Speicherlecks in Tests.

.resolves / .rejects

Du kannst auch den .resolves-Matcher in deiner Expect-Statement verwenden. Jest wartet dann auf die Promise-Auflösung. Bei Ablehnung schlägt der Test automatisch fehl.

test('the data is peanut butter', () => {
return expect(fetchData()).resolves.toBe('peanut butter');
});

Gib die Assertion zurück – ohne return wird dein Test abgeschlossen, bevor das von fetchData zurückgegebene Promise aufgelöst ist und then() den Callback ausführen kann.

Bei erwarteter Ablehnung eines Promises verwende den .rejects-Matcher. Er funktioniert analog zu .resolves. Wenn das Promise erfüllt wird, schlägt der Test automatisch fehl.

test('the fetch fails with an error', () => {
return expect(fetchData()).rejects.toMatch('error');
});

Keine dieser Formen ist den anderen besonders überlegen. Sie können sie innerhalb einer Codebase oder sogar in einer einzelnen Datei kombinieren und nach Bedarf einsetzen. Es hängt einfach davon ab, welcher Stil Ihrer Meinung nach die Tests einfacher macht.