Wie testet man NodeJS-HTTP-Anfragen?

In diesem Node.js-Tutorial erfahren Sie, wie Sie Ihr Testtool einrichten, wie Sie Ihre HTTP GET- und POST-Anforderungen testen und wie Sie ein Fehlerszenario testen.


Ich habe eine Node.js-App, in der ich einige HTTP-Anforderungen testen wollte. Node macht die Dinge normalerweise einfach, also habe ich erwartet, dass dies auch so ist … aber ich habe bald festgestellt, dass dies überhaupt nicht der Fall war.

Beim Komponententest möchten Sie nicht, dass HTTP-Anforderungen hinausgehen und das Ergebnis beeinflussen. Stattdessen erstellen Sie eine gefälschte Anfrage – ein Testdoppel – um die echte zu ersetzen, aber da die Streams von Knoten schwer zu fälschen sind, bin ich hier an die Wand gestoßen.

Es hat etwas Arbeit gekostet, aber einen Weg gefunden, es einfach zu machen! Lassen Sie mich Ihnen anhand einiger Beispiele auf der Grundlage von echtem Live-Produktionscode zeigen, wie das geht.

  • Senden einer GET-Anforderung und Testen der Antwortverarbeitung
  • Senden einer POST-Anforderung und Testen des Parameterverhaltens
  • Testen, ob Fehler korrekt behandelt werden

Beispielszenario

Ich habe dies und die Tests auf realem Code basiert und sie vereinfacht, damit sie leicht zu befolgen sind. Aber die Kerntechniken sind die gleichen, die ich im echten Produktionscode verwende.

Ich werde diese Datei aufrufen api.js

var http = require('http');
 
module.exports = {
  get: function(callback) {
    var req = http.request({
      hostname: 'jsonplaceholder.typicode.com',
      path: '/posts/1'
    }, function(response) {
      var data = '';
      response.on('data', function(chunk) {
        data += chunk;
      });
 
      response.on('end', function() {
        callback(null, JSON.parse(data));
      });
    });
 
    req.end();
  },
 
  post: function(data, callback) {
    var req = http.request({
      hostname: 'jsonplaceholder.typicode.com',
      path: '/posts',
      method: 'POST'
    }, function(response) {
      var data = '';
      response.on('data', function(chunk) {
        data += chunk;
      });
 
      response.on('end', function() {
        callback(null, JSON.parse(data));
      });
    });
 
    req.write(JSON.stringify(data));
 
    req.end();
  }
};

Dies sollte Ihnen bekannt vorkommen, wenn Sie das http-Modul verwendet haben. Eine Funktion zum Abrufen von Daten, eine andere zum Senden von Daten. Der Code verwendet die JSONPlaceholder-API, eine einfache REST-API, die für diese Art von Prototyping und Tests nützlich ist.

Notwendige Bibliotheken

Um das Testen zu vereinfachen, verwenden wir einige Bibliotheken zur Unterstützung. Wir verwenden Mocha als Testframework und Sinon, um die Testdoubles zu erstellen. Streams sind mühsam, aber wir können mock-req und mock-res verwenden, um das zu beheben.

npm install mocha sinon mock-req mock-res

Einrichten der Testsuite

Lassen Sie uns zunächst die Testsuite erstellen. Ich werde diese Datei einfügen tests/apiSpec.js.

var assert = require('assert');
var sinon = require('sinon');
var MockReq = require('mock-req');
var MockRes = require('mock-res');
var http = require('http');
 
var api = require('../api.js');
 
describe('api', function() {
  beforeEach(function() {
    this.request = sinon.stub(http, 'request');
  });
 
  afterEach(function() {
    http.request.restore();
  });
 
 
  
 
});

Die wichtigsten Dinge hier sind beforeEach und afterEach. beforeEach erstellt einen zu ersetzenden Stub http.requestund afterEach stellt die ursprüngliche Funktionalität wieder her.

Ein Stub ist ein Testdouble, mit dem wir das Verhalten definieren und die Nutzung verfolgen können – Rückgabewerte und Parameter festlegen, die Anzahl der Aufrufe überprüfen usw.

Aber wie hilft das? Wir haben den Stub hier erstellt, nicht im API-Modul! NodeJS speichert erforderliche Module im Cache, sodass die Änderungen hier in anderen Modulen widergespiegelt werden. Mit anderen Worten, api.js ist gezwungen, unseren Stub zu verwenden.

Wir speichern den Stub in this.requestdamit wir später darauf verweisen können.

Testen der GET-Anfrage

Beginnen wir mit dem Hinzufügen eines Tests, der die Verarbeitung der Get-Anfrage validiert. Wenn der Code ausgeführt wird, ruft er auf http.request. Da wir es gestubpt haben, ist die Idee, dass wir die Stub-Steuerung verwenden, was wann passiert http.request wird verwendet, damit wir alle Szenarien neu erstellen können, ohne dass jemals eine echte HTTP-Anfrage stattfindet.

it('should convert get result to object', function(done) {
  var expected = { hello: 'world' };
  var response = new MockRes();
  response.write(JSON.stringify(expected));
  response.end();
 
  var request = new MockReq();
 
  this.request.callsArgWith(1, response)
              .returns(request);
 
  api.get(function(err, result) {
    assert.deepEqual(result, expected);
    done();
  });
});

Zuerst definieren wir die erwarteten Daten, die wir in unserem Test verwenden werden, und erstellen ein simuliertes Antwortobjekt. Um zu kopieren, wie http.request funktioniert, schreiben wir eine JSON-Version unserer erwarteten Daten in die Antwort und beenden sie.

Wir erstellen eine Mock-Anfrage, die wir aber nur als Rückgabewert benötigen.

Eine erfolgreiche http.request leitet die Antwort an den Rückruf weiter. Wenn wir uns das vorherige Beispiel ansehen, ist der Rückruf der zweite Parameter, also sagen wir dem Stub, dass er ihn mit der Antwort als Parameter aufrufen soll. Die Nummerierung beginnt bei 0, die Verwendung von 1 bezieht sich also auf den zweiten Parameter. Ein Anruf bei http.request gibt immer eine Anfrage zurück, also sagen wir ihm, dass es unsere Scheinanfrage zurückgeben soll.

Nachdem das Setup abgeschlossen ist, rufen wir an api.get mit einem Rückruf, der das Verhalten verifiziert. Das Assertion-Modul ist in den Knoten integriert, aber wenn Sie es vorziehen, können Sie stattdessen Ihre bevorzugte Assertion-Bibliothek wie Chai verwenden.

Wir können den Test mit ausführen mocha. Sie sollten die Testpässe mit einem grünen Licht finden.

Testen der POST-Anfrage

Für das POST-Szenario möchten wir sicherstellen, dass die Parameter korrekt an die HTTP-Anforderung übergeben werden.

it('should send post params in request body', function() {
  var params = { foo: 'bar' };
  var expected = JSON.stringify(params);
 
  var request = new MockReq({ method: 'POST' });
  var write = sinon.spy(request, 'write');
 
  this.request.returns(request);
 
  api.post(params, function() { });
 
  assert(write.withArgs(expected).calledOnce);
});

Beim Anrufen api.post, sollte es Parameter im Anforderungstext senden. Deshalb müssen wir in diesem Test nur verifizieren request.write wird mit dem richtigen Wert aufgerufen.

Wie zuvor definieren wir zuerst die erwarteten Daten. Dadurch wird der Test einfacher zu warten, da wir nicht überall magische Werte verwenden.

Anschließend definieren wir eine Scheinanforderung und stellen sicher, dass sie die POST-Methode verwendet. Um sicherzustellen, dass die erwarteten Daten geschrieben werden, müssen wir überprüfen, ob die Funktion aufgerufen wurde. Dies erfordert ein weiteres Testdouble, in diesem Fall einen Spion. Genau wie echte Geheimagenten gibt uns ein Spion Auskunft über sein Ziel – in diesem Fall die Schreibfunktion.

Das http.request Die Funktion muss in diesem Test nur die verspottete Anfrage zurückgeben.

Wir nennen api.post, übergeben unsere Parameter und einen leeren Callback. Warum ein leerer Rückruf? Auch hier testen wir nur das, was wir brauchen, und hier müssen wir den Rückruf nicht testen.

Abschließend prüfen wir mit assert die Werte vom Spy: Wir prüfen, ob der Spy einmal mit den richtigen Parametern aufgerufen wurde.

Wir können die Tests erneut mit ausführen mochaund sie werden passieren.

Notiz: In einer echten App möchten Sie vielleicht einen Test für den Rückruf haben, aber er sollte in einen eigenen Testfall gehen. Ein einzelner Testfall sollte idealerweise nur eine Assertion haben. Der Test für einen Callback würde dem Test mit einem Get-Request ähneln, daher gehe ich hier nicht näher darauf ein.

Testen eines Fehlerszenarios

Und zu guter Letzt sollten wir einen Test für einen Fehler einbauen.

Jede andere HTTP-Testbibliothek, die ich ausprobiert habe, ist hier gestolpert, aber mit mock-req ist es unkompliziert.

it('should pass request error to callback', function(done) {
  var expected = 'some error';
  var request = new MockReq();
 
  this.request.returns(request);
 
  api.get(function(err) {
    assert.equal(err, expected);
    done();
  });
 
  request.emit('error', expected);
});

Das sollte Ihnen bekannt vorkommen. Wir definieren die erwarteten Daten, erstellen eine Scheinanforderung, weisen unseren Stub an, sie zurückzugeben, und rufen die API auf. Der an die API übergebene Rückruf überprüft, ob der Fehlerparameter korrekt ist.

Das Neue hier ist die letzte Zeile. Wenn eine HTTP-Anforderung aufgrund von Netzwerkfehlern, HTTP-Parsing-Fehlern oder dergleichen fehlschlägt, wird ein Fehlerereignis ausgegeben. Um einen Fehler zu simulieren, geben wir einen aus dem Test aus.

Führen Sie Tests mit Mokka durch, und sie werden … fehlschlagen? Häh?

Aus diesem Grund sind Unit-Tests wichtig, insbesondere das Testen der Fehlerfälle! Es stellt sich heraus, dass sich ein Fehler in den Code eingeschlichen hat.

Kein Problem, fügen wir eine Behandlung für das Fehlerereignis hinzu.

get: function(callback) {
  var req = http.request({
    hostname: 'jsonplaceholder.typicode.com',
    path: '/posts/1'
  }, function(response) {
    var data = '';
    response.on('data', function(chunk) {
      data += chunk;
    });
 
    response.on('end', function() {
      callback(null, JSON.parse(data));
    });
  });
 
  req.on('error', function(err) {
    callback(err);
  });
 
  req.end();
},

Das einzige, was hier hinzugefügt wird, sind die drei Zeilen zur Behandlung des Fehlerereignisses.
Führen Sie Tests mit Mokka durch und jetzt werden sie bestanden. Fertig!

Fazit

Das Schreiben solcher Tests erfordert ein wenig Übung. Sie müssen wissen, wie die Stubbed- und Mocked-Funktionen funktionieren, aber mit etwas Erfahrung wird es einfach. Das Tolle ist, dass Sie diese Methoden auch anwenden können, um andere Dinge zu testen – MySQL-Abfragen, Redis-Lookups, Ajax-Aufrufe, TCP-Sockets … die Liste geht weiter und weiter.

Ich habe das Beispielmodul und die Tests hier für Sie zum Download bereitgestellt: api.js, apiSpec.js.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *