Node Express TDD with RESTful API in Intellij

Posted on Monday, April 20, 2015



As I start developing in node from scratch I want to do it right.  I installed the Node plugin for Intellij and poked at it a bit (the write up is at http://www.whiteboardcoder.com/2015/04/intellij-and-nod.html )
I also set up mocha with chai for my TDD testing see http://www.whiteboardcoder.com/2015/04/intellij-and-nodejs-setting-up-tdd.html .


I want to go through setting up a simple RESTful API that returns a simple JSON object.  I want to develop this all via TDD (Test Driven Development).




Before I dive in I want to give a few good references I used to help me out http://webapplog.com/tdd/ [1] and https://vimeo.com/105382485 [2] In fact go watch the vimeo video it's well done.

I will be doing this in Intellij.  You don't need Intellij, I just wanted to capture how to do it in Intellij for my own future reference.







Create a new Project


I am going to assume you have Node installed and you have the NodePlugin installed for Intellij.





From the menu select File -> New -> Project









Select Node.js and NPM and click Next.









Give it a name and click Finish.








This pops up. (for me).

This is supposed to configure the Intellij so that it can have Code Completion on the Base NodeJS libraries.  I can't seem to set it up right.  So I click cancel (and set it up manually).


To set up code completion for Node base libraries do the following.








From the File menu select Settings








Under Languages & Frameworks -> JavaScript select Libraries










Checkbox nod-DefinitelyTyped and click OK.


(If you don't have this installed you will need to click download,  Select TypeScript Community stubs from the pull down and download and install node)










Setting up tests


Install mocha and chai via the npm command line tool.



Click terminal in the lower left of Intellij



Run the following commands to install the mocha and chai modules.


  > npm install -g mocha
  > npm install chai












Create a test folder




Right click on the project and select New -> Directory.







Name it test









Create a rest-api.test.js file in the tests folder.









Set up a simple test


Before we write the first real test, set up a simple test to test the test system J

Put this code in rest-api.test.js


var assert = require('chai').assert;

describe(
'My App', function() {

   describe(
'Testing equality', function() {
      it(
'1 should equal 1', function () {
        
assert.equal(1, 1);
      });
   });
});


From the command line run the tests


  > mocha test/rest-api.test.js












Add test script to package.json


You can edit the package.json file to add a script to run the tests.

Here is how I edited my file.



{
 
"name": "node_express_tdd_test",
 
"version": "0.0.0",
 
"private": true,
 
"scripts": {
   
"start": "node ./bin/www",
   
"test": "mocha --recursive test"
 
},
 
"dependencies": {
   
"body-parser": "~1.12.0",
   
"cookie-parser": "~1.3.4",
   
"debug": "~2.1.1",
   
"express": "~4.12.2",
   
"jade": "~1.9.2",
   
"morgan": "~1.5.1",
   
"serve-favicon": "~2.2.0"
 
}
}


Now I can run this from the command line to run the script.


  > npm test
















Testing on Intellij


To set the test up on Intellij (Push button vs command line)





Click on Edit Configurations on the top.










Click +









Select Mocha









Click here and locate the test directory









Change its name








Change the User Interface to tdd








Add --recursive to the mocha options.  (This will run test in subdirectories)









Make sure the mocha package is correct.





Click OK



Run the tests







That ran.  OK now the testing is set up and working… now onto writing the actual test.









Install superagent






Install superagent, I will be using it for testing. 



  > npm install superagent







For more information on superagent check out their github page at https://github.com/visionmedia/superagent [3]








Write the test for the next desired feature


What is the first feature I want? 

Feature:
/hello should return 200 status.



var assert = require('chai').assert;
var request = require('superagent');

describe('My App', function() {
    var baseUrl = 'http://localhost:3000';

    describe('When requested at /hello', function () {
        it('should return 200 code', function (done) {
            request.get(baseUrl + "/hello").end(function(err, res) {
                assert.equal(res.status, 200);
                done();
            });
        });
    });
});


Since this is an asynchronous call you have to use done() as a callback to finish the test when the async is complete.




Run the test



As Expected a failure occurred.









Update app.js


The default created app.js is a little too complex for starting something very simple.

Update app.js to the following.


var express = require('express');

var app = express();
var server;

var start = exports.start = function start(port, callback) {
  console.log("Starting Server on port: " + port)
  server = app.listen(port, callback);
};

var stop = exports.stop = function stop(callback) {
  console.log("Stopping Server")
  server.close(callback);
};

app.get('/hello', function sendResponse(req,res) {
    console.log("Calling Hello World RESTful API")
    res.status(200).send('Hello World!');
});




Also update the bin/www file (the script Intellij is using to run the program.

I updated mine to


var app = require('../app');

app.start(3000);










Update rest-api.test.js


Update the test to Start/Stop the server


var assert = require('chai').assert;
var request = require('superagent');

describe('My App', function() {
    var myApp = require('../app.js');
    var port = 3000;
    var baseUrl = 'http://localhost:' + port

   
before(function(done) {
        myApp.start(port, done);
    });

    after(function(done) {
        myApp.stop(done);
    });

    describe('When requested at /hello', function () {
        it('should return 200 code', function (done) {
            request.get(baseUrl + "/hello").end(function(err, res) {
                assert.equal(res.status, 200);
                done();
            });
        });
    });
});




Now run the test.







Now they pass.






Or alternatively you can run them from the command line.


  > npm test











Write the test for the next desired feature


Feature:
/hello should
·         return a JSON object
·         The JSON has an object named "msg"
·         "msg" is a String
·         The String begins with "Hello"

Test content-type = application/json


Add a new test, check for content-type = "application/json"


var assert = require('chai').assert;
var request = require('superagent');

describe(
'My App', function() {
   
var myApp = require('../app.js');
   
var port = 3000;
   
var baseUrl = 'http://localhost:' + port

   
before(function(done) {
       
myApp.start(port, done);
    });

    after(
function(done) {
       
myApp.stop(done);
    });

    describe(
'When requested at /hello', function () {
        it(
'should return 200 code', function (done) {
           
request.get(baseUrl + "/hello").end(function(err, res) {
                
assert.equal(res.status, 200);
                done();
            });
        });
    });


    describe(
'When requested at /hello', function () {
        it(
'should return application/json with "msg" object',
                                                                      function (done) {
           
request.get(baseUrl + "/hello").end(function(err, res) {
               
assert.equal(res.headers['content-type'], 'application/json');
                done();
            });
        });
    });

});




Run the test






It fails, because we are not sending json yet.

Go update the code.


app.get('/hello', function sendResponse(req,res) {
  console.log("Calling Hello World RESTful API")
  res.json({"not-msg":42})
});


Now it sends back a JSON file with an object named :msg:


Test it again…



It fails…. Why?






It's expecting a content-type of
            "application/json"
But it gets back
            "application/json charset=utf-8"

It has additional information….


Rather than using assert.equal I can use assert.include.

I Updated the test again.





    describe(
'When requested at /hello', function () {
        it(
'should return application/json with "msg" object', function
                                                                                                        (done) {
           
request.get(baseUrl + "/hello").end(function(err, res) {

               
assert.include(res.headers['content-type'], 'application/json');
                done();
            });
        });
    });



Now it will check if the content-type contains application/json.

Run the test again







It succeeds


But it's not done yet that test also needs to confirm that it has an object named "msg" and that object is a String.

How do you test JSON in chai?

I found this resource http://chaijs.com/plugins/chai-json-schema[4] a chai plugin to test against JSON.

Install this plugin


  > npm install chai-json-schema










Dealing wich chai-json-schema error


I got a dependency error.
"peerinvalid The package chai does not satisfy its siblings' peerDependencies requirements! "

If I am reading that correctly it wants chai version 1.6.1 or greater but less than chai version 2

If I run

  > npm ls chai




Looks like I have version 2.2.0

Poking around….

In the package.json included in the cha-json-schema it has this


"peerDependencies": {
 
"chai": ">= 1.6.1 < 2"
}




Looking at their github page

Looks like there have not been any updates in while.
There is a chai2-json-schema, I am going to uninstall chai-json-schema and install chai2-json-schema


  > npm uninstall chai-json-schema
  > npm install chai2-json-schema




Hooray for forking open source projects on github.









Test for JSON schema


JSON does not have a schema set up like XML.  In the past I have had used XML schemas, but I have yet to set up a JSON schema.  

If you are unfamiliar with schemas here is the short… short version.  A schema defines your data format, what is allowed, and how the data is formatted.   JSON does not have a formal schema format, the JSON standard is straight, simple, and to the point.  Schema's can be heavy handed, but sometimes you just need them.

The JSON schema standard is "JSON schema" their website is at http://json-schema.org/ [5] a good read about JSON schema can be found at http://spacetelescope.github.io/understanding-json-schema/UnderstandingJSONSchema.pdf [6] (I printed this guy out and I am reading through it all, and will probably do so a few times)

Create the schema to test against the JSON.


{
   
"$schema": "http://json-schema.org/draft-04/schema#",
   
"title": "Message Schema v1",
   
"type":"object",
   
"required":["msg"],
   
"properties":{
       
"msg":{
           
"type":"string",
           
"pattern": "Hello.*"
       
}
    }
}




Take this schema and this JSON


{
"msg":"Hello there"
}


Over to http://jsonschemalint.com/draft4/# [7] to test the JSON against the schema





It looks good.  Now add it to the test.


Here is my updated test code.



var chai = require('chai')
var assert = chai.assert;
var request = require('superagent');
chai.use(require('chai2-json-schema'));

describe('My App', function() {
    var myApp = require('../app.js');
    var port = 3000;
    var baseUrl = 'http://localhost:' + port
   
var msgSchema = {
       
"$schema": "http://json-schema.org/draft-04/schema#",
       
"title": "Message Schema v1",
       
"type":"object",
       
"required":["msg"],
       
"properties":{
           
"msg":{
               
"type":"string",
               
"pattern": "Hello.*"
           
}
        }
    };


    before(function(done) {
        myApp.start(port, done);
    });

    after(function(done) {
        myApp.stop(done);
    });

    describe('When requested at /hello', function () {
        it('should return 200 code', function (done) {
            request.get(baseUrl + "/hello").end(function(err, res) {
                assert.equal(res.status, 200);
                done();
            });
        });
    });


    describe('When requested at /hello', function () {
        it('should return application/json with "msg" object',
                                                                                    function (done) {
            request.get(baseUrl + "/hello").end(function(err, res) {
                console.log(res);
                assert.include(res.headers['content-type'],
                                                                  'application/json');
                assert.jsonSchema(res.body, msgSchema);
                done();
            });
        });
    });
});




Run the test







It fails.







The JSON I am sending is purposely bad and fails the schema test.

Update the JSON to the following



app.get('/hello', function sendResponse(req,res) {
  console.log("Calling Hello World RESTful API")
  res.json({"not-msg":42,
                    msg:42})
});


I did not remove the "invalid" JSON object I just added a new one.








Run the test




It fails again.  This time it sees the msg object, but it also sees the message object is a number and not a string.

Update the JSON to the following


app.get('/hello', function sendResponse(req,res) {
  console.log("Calling Hello World RESTful API")
  res.json({"not-msg":42,
                    msg:"TEST"})
});









It fails again.  It fails because I am testing the string against a regular expression.  The String must begin with "Hello"

Update the JSON to the following


app.get('/hello', function sendResponse(req,res) {
  console.log("Calling Hello World RESTful API")
  res.json({"not-msg":42,
                    msg:"Hello World!"})
});










Run the test



Now they all pass!


I think I am going to like node. J


 I did leave in the extra bit in the JSON as a test


app.get('/hello', function sendResponse(req,res) {
  console.log("Calling Hello World RESTful API")
  res.json({"not-msg":42,
                    "msg":"Hello World!"})
});


The Schema ignores any extra objects in the JSON.   I am going to remove this extra not-msg and run the test one more time



app.get('/hello', function sendResponse(req,res) {
  console.log("Calling Hello World RESTful API")
  res.json({"msg:"Hello World!"})
});







Run the Test



It passes





Another JSON schema test


Turns out I did not define my Regex expression correctly.  It will accept messages that contain "Hello" rather than start with  Hello.

If I update my app.js code to the following


app.get('/hello', function sendResponse(req,res) {
  console.log("Calling Hello World RESTful API")
  res.json({"not-msg":42,
                    "msg":"This is the start Hello World!"})
});


It will pass all tests.

The Schema pattern needs to be updated in the rest-api.test.js


var msgSchema = {
   
"$schema": "http://json-schema.org/draft-04/schema#",
   
"title": "Message Schema v1",
   
"type":"object",
   
"required":["msg"],
   
"properties":{
       
"msg":{
           
"type":"string",
           
"pattern": "^Hello.*$"
       
}
    }
};




Now run the test.



It fails, because



The pattern does not match.


Update my app.js code to the following


app.get('/hello', function sendResponse(req,res) {
  console.log("Calling Hello World RESTful API")
  res.json({"not-msg":42,
                    "msg":"Hello World!"})
});


Run the tests again



Now they all pass
J




References

[1]        How To Use Mocha With Node.js For Test-Driven Development to Avoid Pain and Ship Products Faster
                Accessed 4/2015
[2]        Test-driven Development of Web Apps in Node.js https://vimeo.com/105382485
                Accessed 4/2015
[3]        Superagent github page
                Accessed 4/2015
[4]        chai-json-schema
                Accessed 4/2015
[5]        JSON schema
                Accessed 4/2015
[6]        Understanding JSON Schema
                Accessed 4/2015
[7]        JSON Schema Lint
                Accessed 4/2015


1 comment: