This is a template express application that utilises the RHMAP (Red Hat Mobile Application Platform) express middleware and demonstrates strong unit testing. It is an http server that uses the express framework to expose a simple API.
The API exposes the following endpoints:
Get a user with the given ID, e.g GET http://127.0.0.1:8001/users/0.
Create a new user with the given ID, e.g POST http://127.0.0.1:8001/users/0, and passing a body of type application/json.
Update a user with the given ID, e.g PUT http://127.0.0.1:8001/users/0 and pass in a JSON body with header "Content-Type: application/json".
{
"firstname": "jane"
"lastname": "doe"
}
For example, try this cURL from a terminal:
curl -X POST --data '{"lastname":"linux"}' -H "Content-Type:application/json" http://127.0.0.1:8001/users/0
And now call this to see the change was saved:
curl http://127.0.0.1:8001/users/0
- node.js v0.10.30
- npm
- redis
Before trying to run this application you'll need to install the dependencies. You can do so by typing npm install in the project directory.
Rather than using a task runner like gulp, grunt, or whichever other task runner you're familiar with, we've chosen to simply use the package.json "scripts" that npm supports. This makes it easy to use local dependencies and reduce bloat. Of course, this project can easily be modified to use your favourite task runner if you like.
Scripts can be run by typing:
npm run-script {SCRIPT_NAME}
You can view the package.json to see the scripts in detail , but here's a breakdown of each.
Start the application, e.g npm start
Check code quality using JSHint.
Execute JSHint, unit tests, and verification of code coverage. This would make sense to run on a CI server.
Execute unit tests and generate code coverage from them.
Verify that code coverage is above a certain threshold.
Execute the unit tests.
This template contains an entry point, application.js that can be started by typing npm start. You can also start the application using node application.js, but this will not set the NODE_PATH environment variable and will therefore cause require paths to be deemed incorrect.
This application is a fairly typical express application, but unlike many examples you might find online, the entire route logic is not contained in single file. Why not? Because that's an awful thing to do in a complex real world application.
We've broken our application down into routers, and modules that handle application logic. If you read lib/routes/users.js you'll see that the majority of the logic it performs is HTTP related, and we allow another module do the real "users" related work. Why do this? Well, we've a few reasons:
The code doesn't sprawl into callback hell, etc. It's also easier to reason about code that is concise.
Modular code is much easier to thoroughly test. Mocking out all the potential scenarios and paths in a 200 line function is tough, but for a 20 line function it's pretty easy.
Why make a router, something that is responsible for glue-ing application logic to HTTP, be responsible for all the heavy lifting?
To issue a request to the express server type http://127.0.0.1:8001/users/1 into the address bar of your web browser. If it works a JSON object will be returned contain one of our sample users from lib/users/index.js. Remember to start the application first by typing npm run-script serve.
Issue the command npm run-script unit to run unit tests that are included.
The unit tests do the following:
- Tests our users interface lib/users/index.js
- Tests our users router lib/routers/users.js
- Tests our users route handling logic lib/route-handlers/users.js
Code coverage can be obtained by running npm test. This also runs the unit tests, since this is required to determine code coverage. This application has 100% code coverage. This does not mean it contains absolutely no errors, but it does mean you can be more confident in using it.
Code coverage for this application is generated using thr istanbul tool.
We use the Mocha test framework to structure our tests. Mocha is popular, mature, and has support for asynchronous test cases.
Here's a sample test case:
var assert = require('assert');
describe('userModule', function () {
describe('#someFunction', function () {
it('should exec successfully', function () {
var result = require('./users').someFunction();
assert.equal(result, 'some test result');
})
});
});
and here's the same test if it was asynchronous i.e it performs some I/O bound operation, or something wrapped in a setTimeout/setImmediate/process.nextTick. Notice we have Mocha inject a done callback, that we can be call to signify our test has completed.
var assert = require('assert');
describe('userModule', function () {
describe('#someFunction', function () {
it('should exec successfully', function (done) {
var result = require('./users').someFunction(function (error, result) {
assert.equal(result, 'some test result');
// Not calling this makes the test fail after 2 seconds (default timer)
done();
});
})
});
});
To mock out dependencies we use proxyquire and sinon. proxyquire allows you to use it in place of the require statement to load a module, and stub out some of its dependencies. By doing this we can force certain scenarios to occur, andtherefore simplify testing. sinon can create our stubs that we will injectusing proxyquire, and as a result allow us to mock out the behaviour of dependencies using a common framework.
There are many methods available for testing express router logic. You can:
- Use the supertest module
- Start the application.js and make real HTTP requests to it
- Manually invoke route handlers
We've used option #1 from the list above since it affords us a number of advantages:
- We can easily mock out dependencies using techniques discussed earlier
- No need to run the web service and make "real" HTTP calls like you would with option #2
- Routes can be tested in isolation since we don't run the entire application, assuming we create our routes modules as functions that must be invoked with an express object passed in
- Requests will be run through the express middleware stack so request objects we test with will be identical to real world counterparts.