Skip to main content

Testing HTTP Responses in Node.js

It generally goes without saying these days that testing is an important part of the development process. However, testing is not always the easiest thing to master, and it can be difficult to know where to start, especially when it comes to learning a new language. Node.js adds an extra twist on this difficulty, in that your code is running Asynchronously, and you will often be dealing with HTTP calls.

Having just been through the process of learning Node.js and working on a simple application that deals with calling and receiving responses from an external API, the most painful thing I found was that there aren't many tutorials on how to test HTTP requests and responses that I find particularly clear. So, this tutorial aims to provide clear information on how to effectively test your requests and responses, with a little bit about testing emitted events.

Setting up the Test Environment

For these tests we'll be using the Mocha testing framework along with the Chai assertion library. We'll also use the popular Request library to send requests to our server.

Create a new folder for you project and install Mocha, Chai, and Request to it by running:

> npm install mocha chai request

By default, Mocha looks for tests in a folder called /test within the project root, so create the folder with:

> mkdir test

In order to run our tests we'll set up a Makefile in the project root, the code of which looks like this:

REPORTER = spec

test:
    @NODE_ENV=test ./node_modules/.bin/mocha \
        --reporter $(REPORTER) \
        --ui tdd

test-w:
    @NODE_ENV=test ./node_modules/.bin/mocha \
        --reporter $(REPORTER) \
        --growl \
        --ui tdd \
        --watch

.PHONY: test test-w

Be careful when you're creating the Makefile that you indent the lines with Tab characters and not spaces.

You can now run your tests once with:

> make test

You should see something like 0 passing (2ms), which indicates that the test suite ran but didn't find any tests. If you see an error then the most likely options are probably that you've got spaces in your Makefile, or that Make can't find Mocha, in which case check that you have a /node_modules directory in your project folder and that Mocha is installed to it.

Alternatively, you can also run the tests continuously in the background with:

> make test-w

The make test command should also be added to the scripts section of your /package.json file, and Mocha, Chai, and Request to devdependencies; like so:

{
  "scripts": {
    "test": "make test"
  },
  "devDependencies": {
    "mocha": "*",
    "chai": "*",
    "request": "*"
  }
}

Creating our Server

Next we'll create the server that we'll be testing (Yes, by most standards writing the code before the tests is backasswards, but this is for demo purposes). This server will listen for requests and respond according to the content of the request header. If the request has a Content-Type header of 'text/plain' it will respond with an HTTP 200 "OK" code and emit an Event called success containing the body of the request. For any other Content-Type header we will return an HTTP 400 "Bad Request" error.

Create a server.js file in a new lib folder in your project directory, with the code below:

// ./lib/server.js

var http = require('http');

var server = module.exports = http.createServer(function (req, res) {

  if (req.headers['content-type'] === 'text/plain') {

    var body = '';
    req.on('data', function (chunk) {
      body += chunk.toString();
    });

    req.on('end', function () {
      res.writeHead(200, {'Content-Type': 'text/plain'});
      res.end('correct header');
      server.emit('success', body);
    });

  } else {
    res.writeHead(400, {'Content-Type': 'text/plain'});
    res.end('wrong header');
  };
});

Setting up the Tests

Before we write our first test, we need to do a little more setup. The first thing we will need to do is to require Chai's expect function in our test suite. So we'll create a file in our /test folder called tests.js:

> touch test/tests.js

And with the first two lines we'll require the expect function from Chai:

var expect = require('Chai').expect;

You may have noticed that our server doesn't start automatically, as it has no listen call. The second line of our server code contains a module export that allows our server to be imported into another file. We can then start the server before our tests run, and close it when they finish. To do this we need to import our server into tests.js, and within the main 'describe' function in which we write our tests we'll include 'before' and 'after' functions.

// ./test/tests.js

var server = require('../lib/server.js');

describe('server response', function () {
  before(function () {
    server.listen(8000);
  });

  after(function () {
    server.close();
  });
});

Writing the Response Tests

We can now get round to actually writing our tests. We'll write three tests, in order to test each response from our server; the incorrect header response, the successful header response, and the data emitted on a successful response. First, we need to require the Request library at the beginning of tests.js:

var request = require('request');

Now we can include the test within our 'describe' function, immediately below the 'after' function:

it('should return 400', function (done) {
  request.get('https://localhost:8000', function (err, res, body){
    expect(res.statusCode).to.equal(400);
    expect(res.body).to.equal('wrong header');
    done();
  });
});

The it statement, just like describe, before, and after, follows the BDD standard for writing tests. We begin by describing what the test should return, and then a function containing the tests. Because our code is Asynchronous, we need to invoke a callback (in this case called done) so that Mocha knows to wait until the test is complete.

The second line begins our request to the server. We're calling request with a GET (though it could just as well be POST) and passing in the address of our server; in this case localhost listening on port 8000. There is also a function with error, response, and body callbacks. Within the function we're listening for a response, and this is where the test really happens.

The Chai assertion library works by chaining together natural language assertions, making the tests fairly easy to read. In this case we're expecting the StatusCode of the response to equal 400 and the body of the response to read wrong header. The final part of the code is the done() callback; without which the test will exit with a timeout error.

Our second test will test the response if the request contains the Content-Type: text/plain header.

it('should return 200', function (done) {
  var options = {
    url: 'https://localhost:8000',
    headers: {
      'Content-Type': 'text/plain'
    }
  };
  request.get(options, function (err, res, body) {
    expect(res.statusCode).to.equal(200);
    expect(res.body).to.equal('correct header');
    done();
  });
});

This test is much like the first, expect we've added an options object containing the destination url and headers of our request. This is passed as the first parameter of the request, so that our request will be sent to https://localhost:8000 with the correct Content-Type header. By creating an options object like this it become very easy to use the Request library to create a request with any parameters we'd like.

These two tests show the basic form for creating thorough HTTP Server tests. By adding more expect statements and adding different parameters to the chain, you can easily test all the different parts of your responses.

Testing Emitters

Now that we can test our responses, I figure that the next step might be to test what our code is emitting internally. While not technically part of the scope of this article, if you're developing and testing HTTP Requests and Responses in Node.js, at some point your going to be using event emitters and listeners, and in fact you already have. In our server the code req.on('data', function () {}); is an event listener, listening for the data event that is emitted when we receive data. Same with the req.on('end') function.

As you can see if you look back at our server code, once we receive the end event, we send out our own success event that contains the body of the received request. To test that we're successfully emitting, we'll add a body to our request, and we'll need to set a timeout in our test to make sure that the event is firing within a reasonable time.

it('should emit request body', function (done) {
  var options = {
    url: 'https://localhost:8000',
    headers: {
      'Content-Type': 'text/plain'
    },
    body: 'successfully emitted request'
  };
  var eventFired = false;

  request.get(options, function (err, res, body) {});

  server.on('success', function (data) {
    eventFired = true;
    expect(data).to.equal('successfully emitted request');
  });

  setTimeout( function () {
    expect(eventFired).to.equal(true);
    done();
  }, 10);
});

We have now attached a body to our request, that should be emitted back to us, and an eventFired variable set to false, that will be set to true when the event fires. The expect statements are no longer in the request function, as we are not testing the HTTP response. The code server.on('success', function (data) {}) listens for the success event, and passes the data from that event into a function. Within the function we set eventFired to true, and test to see that the emitted data is correct. The setTimeout function is required in order to properly test that the event fired. If the event does not fire, then the test will fail gracefully rather than with a timeout error.

Conclusion

We now have some basic tests that should reasonably cover the server code. This basic setup should work with most Node.js projects, and the general idea of this sort of testing is an essential skill in any programming language. If you'd like to have a look at or download and test the full code yourself then it can be found here.