Previous Post
Next Post
The project:
https://github.com/liquid-co-ops/liqueed
was built using TDD (Test-Driven Development) worflow. In the previous post I showed a logic service example, implemented using tests. Today, I want to show that the MVC controllers was also coded using TDD. The controllers are JavaScript modules that expose functions as actions to be routed by Express. The actions receive and return JSON objects. They are the basis for the exposed API, to be consumed by the clients (actually, only one client, a simple single page application). For example, controllers\personapi.js starts declaring:
'use strict';
var service = require('../services/person');
It consumes the person service module.
There are associated test at test\personapi.js, its initial imports:
'use strict';
var controller = require('../controllers/personapi');
var loaddata = require('../utils/loaddata');
var db = require('../utils/db');
var async = require('simpleasync');
In the previous post, I mentioned that the test granularity I prefer for JavaScript is the module, not the function. So, all the tests in the module are execute, in declaration order. The first test is in charge of inicialization the domain model:
var persons;
exports['clear and load data'] = function (test) {
var personService = require('../services/person');
test.async();
async()
.then(function (data, next) { db.clear(next); })
.then(function (data, next) { loaddata(next); })
.then(function (data, next) { personService.getPersons(next); })
.then(function (data, next) {
persons = data;
test.ok(persons);
test.ok(persons.length);
test.done();
})
.run();
};
The new thing to understand is the use of the module simpleasync, pointed by the async variable. I wrote the module to chain functions. Each function receives two arguments: data, the success result of the previous executed function in chain, or the initial value triggered in the run chain function. And next, a callback to be invoked by the function, to execute the rest of the chain. The callback receives two arguments: err and data. So it can be used as the callback of other functions. If err is not null, the next functions in chain is not executed and the function defined in the chain fail method is run (this option is not used in the above code). In the above example personService.getPersons(next) invokes the retrieve of the person list, using next as callback. The next chained function receives the person list in the data argument, and then, it saves it in a module variable, ready to be used by the rest of the tests.
It is not using a database. It using an in-memory domain model. That is the default “persistence”, and it is used in many of the defined tests. The initial domain model is loaded from testdata.json using the loaddata function:
{
"projects": [
{
"name": "FaceHub",
"periods": [
{
"name": "January 2014",
"date": "2014-01-31",
"amount": 100,
"assignments": [
{ "from": "Alice",
"to": "Bob",
"amount": 50,
"note": "Arrive earlier" },
{ "from": "Alice",
"to": "Charlie",
"amount": 50 ,
"note": "Arrive earlier" },
{ "from": "Bob",
"to": "Alice",
"amount": 60 ,
"note": "Arrive earlier" },
{ "from": "Bob",
"to": "Charlie",
"amount": 40 ,
"note": "Arrive earlier" },
{ "from": "Charlie",
"to": "Alice",
"amount": 35 ,
"note": "Arrive earlier" },
{ "from": "Charlie",
"to": "Bob",
"amount": 65 ,
"note": "Arrive earlier" }
]
},
{ "name": "February 2014",
"date": "2014-02-28",
"amount": 100 }
],
"team": [ "Alice", "Bob", "Charlie" ],
//....
The module personapi.js exports some functions to be used as actions:
module.exports = {
list: list,
get: get,
getProjects: getProjects,
loginPerson: loginPerson,
getPendingShareProjects:getPendingShareProjects,
updatePassword: updatePassword
}
Topics for the next posts: more API tests, routing of actions, persistence, etc.
Stay tuned!
Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez