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.
Ahh found this post https://github.com/Bartvds/chai-json-schema/issues/13
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
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
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
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
Great article Patrick ... Thanks
ReplyDelete