Unabhängige Unit-Tests: HTTP-Anfragen abstrahieren

Das Erzeugen und Verwenden von Stub- und Mock-Objekten fällt in JavaScript aufgrund des dynamischen Typsystems verhältnismäßig leicht. Aufwendig wird es allerdings, wenn eine gewisse Komplexität erreicht wird, wie beim Simulieren des HTTP-Protokolls. Warum also nicht für derartige Aufgaben ein spezialisiertes Framework verwenden?

In Pocket speichern vorlesen Druckansicht
Lesezeit: 4 Min.
Von
  • Golo Roden
Inhaltsverzeichnis

Um Komponenten unabhängig voneinander testen zu können, greift man in der Regel auf Stub- und Mock-Objekte zurück. JavaScript und auch Node.js bilden hierzu keine Ausnahme. Aus diesem Grund existieren diverse Frameworks, die den Entwickler beim Erzeugen und Verwenden dieser Objekte unterstützen, beispielsweise das äußerst empfehlenswerte Sinon.js.

Dennoch kann es sinnvoll sein, für ausgewählte Szenarien spezialisierte Frameworks einzusetzen. Ein derartiges Framework ist nock, das von Pedro Texeira entwickelt wird und auf das Simulieren von HTTP-Anfragen spezialisiert ist.

Die Installation von nock erfolgt auf dem üblichen Weg mit npm (Node Package Manager) in den lokalen Kontext der Anwendung:

$ npm install nock

Anschließend kann man die require-Funktion aufrufen, um das Modul in die eigenen Unit-Tests zu integrieren:

var nock = require('nock');

Um eine HTTP-Anfrage zu simulieren, muss man nock im nächsten Schritt die Adresse des zu simulierenden Webservers übergeben und für die Kombination aus verwendeter Methode und Route die gewünschte Antwort hinterlegen. Um beispielsweise zu simulieren, dass das Hinzufügen eines neuen Anwenders funktioniert hat, genügt der folgende Code:

nock('http://api.example.com')
.post('/users', { lastName: 'Roden', firstName: 'Golo' })
.reply(201, { id: 'e204e508-10a5-4d38-86fe-35e4de99a79e' });

Stellt die Anwendung nun eine passende Anfrage an die genannte Adresse, fängt nock diese Anfrage ab und reagiert, indem es den Statuscode "201" und die angegebene ID als JSON-Objekt zurücksendet.

Nachdem nock reagiert hat, markiert es die Anfrage als beantwortet. Wenn die Anwendung die gleiche Anfrage zu einem späteren Zeitpunkt erneut stellt, lässt nock sie passieren. Wenn man auch diese zweite Anfrage abfangen will, muss man nock mehrfach mit der gleichen Konfiguration aufrufen.

Anstelle eines JSON-Objekts kann man bei Bedarf alternativ eine Zeichenkette übergeben, oder – wenn man statt reply die Funktion replyWithFile verwendet – auch eine beliebige Datei.

Um statt einer HTTP- eine HTTPS-Anfrage zu simulieren, genügt es, nock eine HTTPS-Adresse zu übergeben. Zusätzlich kann man auch einen alternativen Port angeben, um einen anderen als den standardmäßig vorgesehenen Port 80 beziehungsweise 443 zu verwenden.

Außer der Funktion post stellt nock weitere Funktionen zur Verfügung, die auf verschiedene andere HTTP-Verben reagieren: get, put, delete, head, patch und merge. Darüber hinaus ist es möglich, auf jedes beliebige Verb zu reagieren, indem man die Funktion intercept verwendet, und dieser als zweiten Parameter das gewünschte Verb übergibt:

nock(...).intercept('/', 'OPTIONS').reply(...);

Ausgesprochen hilfreich ist, dass man sämtliche Funktionen von nock verketten kann, also mehrere Anfragen an einen Webserver auf einmal konfigurieren kann:

nock(...)
.get(...).reply(...)
.post(...).reply(...);

Innerhalb eines Unit-Tests kann es durchaus interessant sein, ob eine HTTP-Anfrage gestellt wurde oder nicht. Zu diesem Zweck liefert der Aufruf von nock ein Objekt zurück, das entsprechende Funktionen anbietet. Die wichtigste dieser Funktionen ist isDone. Sie gibt true zurück, wenn die zugehörige HTTP-Anfrage gestellt wurde, ansonsten false:

var nockedHttpRequest = nock(...).post(...).reply(...);
if (!nockedHttpRequest.isDone()) {
// ...
}

Die Eigenschaft pendingMocks enthält im Fall, dass isDone den Wert false zurückliefert, eine Liste aller nicht aufgerufenen HTTP-Anfragen:

var nockedHttpRequest = nock(...).post(...).reply(...);
if (!nockedHttpRequest.isDone()) {
console.log(nockedHttpRequest.pendingMocks);
}

Nach dem Abschließen eines Unit-Tests empfiehlt es sich, die Funktion cleanAll aufzurufen, um die Simulation aller zwar registrierten, aber nicht abgerufenen HTTP-Anfragen zu entfernen. Nach diesem Aufruf bleibt nock unsichtbar, bis erneut zu simulierende HTTP-Anfragen registriert werden. Es bietet sich daher an, den Aufruf dieser Funktion innerhalb von suiteTeardown unterzubringen:

suite('...', function () {
var nockedHttpRequest;

test('...', function (done) {
nockedHttpRequest = nock(...).post(...).reply(...);
// ...
done();
});

suiteTeardown(function () {
nockedHttpRequest.cleanAll();
});
});

Auf diesem Weg findet jeder Unit-Test einen aufgeräumten Zustand vor und kann nock an die individuellen Bedürfnisse anpassen.

tl;dr: Das Modul nock ist ein unverzichtbares Hilfsmittel, um Code zu testen, der HTTP-Anfragen stellt. Man kann jede einzelne Anfrage simulieren und anschließend prüfen, ob sie tatsächlich gestellt wurde. Als spezialisiertes Framework stellt nock daher eine ausgezeichnete Ergänzung zu Mock-Frameworks wie Sinon.js dar. ()