Testing Express with Mocha and Chai

In this post we are going to create a server app using Express, and we’ll test it with Mocha and Chai. Testing our app allows us to keep control of every change in the code while we are in the development stage.

Building our app

We’ll write a program that listens to requests, and responds appropriately. When it receives a POST request for /set?somekey=somevalue it will store the passed key and value in memory. When it receives a GET request for /get?key=somekey it will return the value stored at somekey.

Let’s create a directory to hold our app, and make it our working directory:

$ mkdir app
$ cd app

Then we’ll run npm init -y to create a package.json file. This file will list all the dependencies necessary for our project, as well as some other metadata.

We can install Express by running:

$ npm install --save express

This command will also include the module in our package.json file as a dependency. It will look like this:

{
  "name": "app",
  "version": "1.0.0",
  "dependencies": {
    "express": "^4.14.0"
  }
}

Let’s check that everything is working by creating the simplest Express app in a file called app.js:

var express = require('express')
var app = express()

app.get('/', function (req, res) {
  res.send('Hello World!')
})

app.listen(4000, function () {
  console.log('We are listening on port 4000!')
})

We can start our server app by running:

$ node app.js

It will start listening on port 4000. We can send it a request by visiting http://localhost:4000 in our browser, or using curl:

$ curl localhost:4000
Hello world!

As it’s pretty common to start server apps using npm start instead of specifying the filename directly, we’re going to add that shortcut to our package.json:

{
  "name": "app",
  "version": "1.0.0",
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "^4.14.0"
  }
}

Ok, now it’s time to write some tests.

Test-driving our app

We are going to test-drive our server app using Mocha and Chai. Mocha is a feature-rich JavaScript test framework running on Node and in the browser, and it’s specially convenient for asynchronous testing.

We’ll install mocha as a development dependency for our project:

$ npm install --save-dev mocha

Chai is an assertion library that integrates very well with Mocha. Let’s install it:

$ npm install --save-dev chai

Since we are going to make requests to our server, we’ll also install chai-http to have nicer assertions:

$ npm install --save-dev chai-http

Let’s also set things up so that running npm test will run our tests with mocha. Our package.json will now look like this:

{
  "name": "app",
  "version": "1.0.0",
  "scripts": {
    "start": "node app.js",
    "test": "mocha"
  },
  "dependencies": {
    "express": "^4.14.0"
  },
  "devDependencies": {
    "chai": "^3.5.0",
    "chai-http": "^3.0.0",
    "mocha": "^3.2.0"
  }
}

Now it’s time to write our first test and see Mocha in action. We’ll create a new file test/app_spec.js:

mkdir test
touch test/app_spec.js

Testing our first endpoint

Our first test will check that our POST requests to /set are received correctly, and the response status is 200:

// test/app_spec.js

var chai = require('chai');
var chaiHttp = require('chai-http');
var app = require('../app');

var expect = chai.expect;

chai.use(chaiHttp);

describe('App', function() {
  describe('/set?somekey=somevalue', function() {
    it('responds with status 200', function(done) {
      chai.request(app)
        .post('/set?somekey=somevalue')
        .end(function(err, res) {
          expect(res).to.have.status(200);
          done();
        });
    });
  });
});

If we try to run our tests with npm test, we’ll encounter this error:

TypeError: app.address is not a function
 at serverAddress (node_modules/chai-http/lib/request.js:252:18)
 at new Test (node_modules/chai-http/lib/request.js:244:53)
 at Object.obj.(anonymous function) [as post] (node_modules/chai-http/lib/request.js:216:14)
 at Context.<anonymous> (test/app_spec.js:13:10)

This is because our app.js file is being required in the test, but isn’t exporting anything. Let’s fix that:

// app.js

var express = require('express')
var app = express()

// ...

module.exports = app

If we run our test again, it will fail with the expected error:

Uncaught AssertionError: expected { Object (domain, _events, ...) } to have status code 200 but got 404
  at test/app_spec.js:15:31
  at Test.Request.callback (node_modules/superagent/lib/node/index.js:631:3)
  at IncomingMessage.<anonymous> (node_modules/superagent/lib/node/index.js:795:18)
  at endReadableNT (_stream_readable.js:974:12)
  at _combinedTickCallback (internal/process/next_tick.js:74:11)
  at process._tickCallback (internal/process/next_tick.js:98:9)

Now let’s implement the /set endpoint. We’ll receive the key and value in the query part of the request, and we’ll store them in a hash in memory:

// app.js

var express = require('express');
var app = express();

var cache = {};

app.post('/set', function(req, res) {
  var query = req.query;
  Object.keys(query).forEach(function(key) {
    cache[key] = query[key];
  });
  res.status(200).end();
});

module.exports = app;

If we run npm test now, everything will be green. 👍

Testing our second endpoint

We’ll write our second test to check that GET requests to our /get endpoint are received correctly, and respond with status 200. We’ll also check that the endpoint returns the value stored in a previous request:

// test/app_spec.js

describe('App', function() {
  // ...

  describe('/get', function() {
    it('responds with status 200', function(done) {
      chai.request(app)
        .post('/set?somekey=somevalue')
        .then(function() {
          chai.request(app)
            .get('/get?key=somekey')
            .end(function(err, res) {
              expect(res).to.have.status(200);
              expect(res.text).to.equal('somevalue');
              done();
            });
        });
    });
  });
});

If we run npm test at this point, we’ll get the following error:

Uncaught AssertionError: expected { Object (domain, _events, ...) } to have status code 200 but got 404
  at test/app_spec.js:29:35
  at Test.Request.callback (node_modules/superagent/lib/node/index.js:631:3)
  at IncomingMessage.<anonymous> (node_modules/superagent/lib/node/index.js:795:18)
  at endReadableNT (_stream_readable.js:974:12)
  at _combinedTickCallback (internal/process/next_tick.js:74:11)
  at process._tickCallback (internal/process/next_tick.js:98:9)

Let’s implement the /get endpoint. It just needs to return whatever is in our cache for the key specified in the request query:

// app.js

var express = require('express');
var app = express();

var cache = {};

app.post('/set', function(req, res) {
  // ...
});

app.get('/get', function(req, res) {
  res.send(cache[req.query.key]);
});

module.exports = app;

Everything’s green again! 👍👍

Now we have a working server listening on port 4000. We can store keys and their values in a hash, and these values can be accessed via their keys very easily. Awesome!

« Week 10 - Individual technical challenges Week 11 - Final project (part 1) »