In the first post, I created the confirmational aspects of a simple event registration web site.  Now I am well set up to use that to drive the functional aspects.

Implement the Functional Behavior


The Existing Event Spec

Let’s look again at the main part of event-spec.js:

vows.describe('Event').addBatch({
  'An Event': {
    'when asked for guests': {
      topic: function () {
        Event.getGuests(this.callback);
      },
      'should return the standard guests':
        function (err, guests) {
          assert.deepEqual (guests,
            ['Bob', 'Sally', 'Tim', 'Joe']);
        }
    },
  'when registering Bob': {
      topic: function() {
        Event.register('Bob', this.callback);
      },
      'should return Bob':
        function (err, guest){
          assert.equal(guest, 'Bob');
        }
     }
  }
}

Adding Event Registration

What I want to do is register the four standard guests, then execute the guest list test to see that those four guests are returned. Vows executes sibling contexts in parallel, so I need to either use separate batches or use a sub-context. I messed around with both, and for now I decided to go with two batches. The basic structure will be:

vows.describe('An Event')
.addBatch({
  'when guests register' : //register each guests
})
.addBatch({
  'when asked for guest list': //get confirmation list
}).export(module);

For the first addBatch (register), I’ll take the current test and move it into that block:

.addBatch({
  'when guests register' : {
    'like Bob': {
      topic: function() {
        Event.register('Bob', this.callback);
      },
      'should return Bob': function (err, guest) {
        assert.equal(guest, 'Bob');
      }
    }
  }
}

This would get repetitive for multiple register calls, so I’ll refactor out the context into a method:

.addBatch({
  'when guests register' : {
    'like Bob': registerGuest()
  }
}
...
function registerGuest() {
  return {
    topic: function() {
      Event.register('Bob', this.callback);
    },
    'should confirm Bob': function (err, guest) {
      assert.equal(guest, 'Bob');
    }
  };
}

Which can be parameterized to:

'like Bob': registerGuest('Bob')
...
function registerGuest(guest) {
  var context = {};
  context.topic = function() {
      Event.register(guest, this.callback);
  };
  context['should confirm ' + guest] = function (err,
    response) {
        assert.equal(response, guest);
    }
  return context;
}

Adding in each of the four expected guests makes the 'when guests register' context:

'when guests register' : {
  'like Bob': registerGuest('Bob'),
  'like Sally': registerGuest('Sally'),
  'like Tim': registerGuest('Tim'),
  'like Joe': registerGuest('Joe')
}

I don’t like the duplication on each line, nor the overall duplication for each guest. I can clean this up by introducing this method:

function registerGuests(){
  var context = {};
  for(arg in arguments){
    var guest = arguments[arg];
    context['like ' + guest] = registerGuest(guest);
  }
  return context;
}

Here is the entire event test suite file at this point:

var vows = require('vows'),
assert = require('assert')

var Event = require('../lib/event').getEvent();

vows.describe('An Event')
.addBatch({
  'when guests register' :
    registerGuests('Bob', 'Sally', 'Tim', 'Joe')
}).addBatch({
  'when asked for guests': {
    topic: function () {
      Event.getGuests(this.callback);
    },
    'should return the standard guests':
      function (err, guests) {
        assert.deepEqual (guests,
          ['Bob', 'Sally', 'Tim', 'Joe']);
    }
  },
}).export(module);

function registerGuests() {
  var context = {};
  for(arg in arguments) {
    var guest = arguments[arg];
    context['like ' + guest] = registerGuest(guest);
  }
  return context;
}

function registerGuest(guest) {
  var context = {};
  context.topic = function() {
    Event.register(guest, this.callback);
  };
  context['should confirm ' + guest] = function (err,
    response) {
      assert.equal(response, guest);
  }
  return context;
}

The corresponding code in event.js is:

exports.getEvent = function () {

  return new Event();

  function Event() {
    var guests = [];
    this.getGuests = function(callback) {
      callback(null, guests);
    };
    this.register = function(guest, callback) {
      guests.push(guest)
      callback(null, guests[guests.indexOf(guest)]);
    };
  }
}

The Data Store Functionality

Yes, this is just a simple in-memory array based solution. I’ve done the simplest possible thing for my purposes right now. It is appealing to be able to validate the system with an in-memory data storage structure, then implement a more appropriate data store. Either way, what I want you to notice is that the technique puts an emphasis on minimizing the functional aspect while providing a design that has cleanly decoupled the actual data store implementation.

For those looking for more on data with node, there are (multiple) open-source modules for CouchDb (see here for more), Mongo, SqlLite and others; plus some light-weight keyed value stores like node-dirty and nStore (also, check this post by Chris Strom where he does both a CouchDB and node-dirty implementation).

Some Loose Ends

I still need that test for registering with a missing name:

'with missing name' : {
  topic: function() {
    theEvent.register('', this.callback);
  },
  'should return an error': function (err, ignore) {
    assert.isNotNull(err);
  }
},

Which I can extract and follow a similar pattern as the register tests in order to expand to multiple ‘bad’ inputs:

'with missing name' : noMissingNames({
  'empty string': '',
  'null': null,
  'white space': '    ',
  'undefined': undefined}),
...
function noMissingNames(missingNames) {
  var context = {};
  for(label in missingNames) {
    context[label] = noMissingName(missingNames[label]);
  }
  return context;
}

function noMissingName(missingName) {
  return {
    topic: function() {
      Event.register(missingName, this.callback);
    },
    'should return an error': function (err, ignore) {
      assert.isNotNull(err);
    }
  };
}

And now that I’m actually using the err parameter for register, I’ll add a check in my registerGuest method to make sure no error is returned on a good confirmation:

function registerGuest(guest) {
  var context = {};
  context.topic = function() {
    Event.register(guest, this.callback);
  };
  context['should not return any errors'] =
    function (err, ignore) {
      assert.isNull(err);
  };
  context['should confirm ' + guest] = function (err,
    response) {
      assert.equal(response, guest);
  }
  return context;
}

The guard clause in event for missing names is:

this.register = function(guest, callback) {
  if(!(guest) || (/^\s*$/).test(guest)) {
    callback ('Name must be provided');
    return;
  }
...

I also added tests and code to make supplying no callback parameter safe (check those out on github if you’re interested).

The Whole Test Suite

Running the event-spec.js test suite now yields:

$ vows --spec event-spec.js

♢ An Event

  when guests register like Bob
    ✓ should not return any errors
    ✓ should confirm Bob
  when guests register like Sally
    ✓ should not return any errors
    ✓ should confirm Sally
  when guests register like Tim
    ✓ should not return any errors
    ✓ should confirm Tim
  when guests register like Joe
    ✓ should not return any errors
    ✓ should confirm Joe
  with missing name empty string
    ✓ should return an error
  with missing name null
    ✓ should return an error
  with missing name white space
    ✓ should return an error
  with missing name undefined
    ✓ should return an error
  when asked for guests
    ✓ should return the standard guests
  No callback provided on getGuests
    ✓ is safe
  No callback provided on register
    ✓ is safe

✓ OK » 15 honored (0.023s)

Create the Functional User Interface


The Registration Templates

I’ll use the same initial technique to create the jade template for registering as I did for the initial registration confirmations:

h1 Register for Larry's Event
form(action: '/guests', method: 'post')
  fieldset
    p
      label(for="name") Name:
      input(name: 'name')
    p
      input(type: 'submit', value="register")

Which renders as:

I create the static, literal output and can again pin it with a test:

'Registration form': getContext('registration',
  function () {
    client.get('/registration', this.callback);
  })

And add the get method to the server code:

app.get('/registration', function (request, response) {
  Render(response, 'signupform');
});

I also need to create a template for the error condition, which is just a variation in the signup form (versus a separate response page):

h1 Register for Larry's Event
form(action: '/guests', method: 'post')
  fieldset
    p#error(style="color: red")
      | Name must be provided
    p
      label(for="name") Name:
      input(name: 'name')
    p
    input(type: 'submit', value="register")

I’ll create another literal template spec:

'Registration form with error': getContext(
  'registration-error', function () {
    client.get('/registration-error', this.callback);
  })

And until these two templates get united via refactor, I’ll create a separate get in the server code:

app.get('/registration-error', function (request,
  response) {
    Render(response, 'registration-error');
});

Now I’ll modify the registration.jade template:

h1 Register for Larry's Event
form(action: '/guests', method: 'post')
  fieldset
    - if (locals.error)
      p#error(style="color: red")= error
    p
      label(for="name") Name:
      input(name: 'name')
    p
      input(type: 'submit', value="register")

The registration-error get can now use the same template and pass in the error:

app.get('/registration-error', function (request,
  response) {
    Render(response, 'registration',
      {error: 'Name must be provided'});
});

The spec tests pass. Now I’ll refactor out a renderRegistration method for both gets:

app.get('/registration', function (request, response) {
  renderRegistration(response, 'registration');
});

app.get('/registration-error', function (request,
  response) {
    renderRegistration(response, 'registration',
      'Name must be provided');
});

function renderRegistration(error) {
  var locals = (error) ? {error: error} : null;
  render(response, 'registration', locals);
}

Thoughts on Application Context

The registration-error ‘get’ can technically be deleted once we get the registration functionality in place. However, I’d like to find a way to preserve the standard tests as a different application context, but that will need to be a problem for another day.

Test-Drive the Integration to the Finish Line


Automated Browser Tests
I’ll bring in one more testing tool to drive our final integration steps. Meet zombie, the ‘Insanely fast, headless full-stack testing using Node.js’. I’ll use zombie in the vows tests to simulate the user browser activity.

There are two tests I’ll write, one for a successful registration, and the other for a missing name registeration:

var vows = require('vows'),
    assert = require('assert'),
    zombie = require('Zombie');

vows.describe('Event Registration').addBatch({
  'Register Guest' : {
    topic: function () {
      var callback = this.callback;
      zombie.visit('http://127.0.0.1:3003/registration',
        function (err, browser, status) {
          browser.fill('name', 'Pablo');
          browser.pressButton('register', callback);
      });
    },
    'browser should respond without error': function (err,
      browser, status) {
        assert.isNull(err);
    },
    'and with http status 200': function (err, browser,
      status) {
        assert.equal(status, 200);
    },
    'and contain the confirmation': function (err, browser,
      status) {
        assert.notEqual(browser.html().indexOf(
          'Thanks for registering Pablo!'), -1);
    }
  },
  'Register with No Name' : {
    topic: function () {
      var callback = this.callback;
      zombie.visit('http://127.0.0.1:3003/registration',
        function (err, browser, status) {
          browser.fill('name', '');
          browser.pressButton('register', callback);
      });
    },
    'browser should respond without error': function (err,
        browser, status) {
          assert.isNull(err);
    },
    'and with http status 200': function (err,
      browser, status) {
        assert.equal(status, 200);
    },
    'and contain the confirmation': function (err,
    browser, status) {
        var error = browser.querySelector("#error");
        assert.ok(error);
        assert.equal(error.textContent,
          'Name must be provided')
    }
  }
}).export(module);

Now we can wire up the registration, here is the server code in its entirety:

express = require('express'),
connect = require('connect'),
app = express.createServer(
  express.bodyDecoder());
app.set('views', __dirname + '/views/');

var Event = require('./lib/event').getEvent();

app.get('/guests', function (request, response) {
  Event.getGuests(function(err, guests) {
    render(response, 'guests', {guests: guests});
  });
});

app.post('/guests', function (request, response) {
  console.log('name', request.param('name'));
  Event.register(request.param('name'),
    function(err, data) {
      if(err) {
        renderRegistration(response, err);
      } else {
        render(response, 'confirmation', {guest: data});
      }
    });
});

app.get('/registration', function (request, response) {
  renderRegistration(response);
});

function renderRegistration(response, error) {
  var locals = (error) ? {error: error} : null;
  render(response, 'registration', locals);
}

function render(response, template, locals) {
	var options = { layout: false };
	if(locals) { options.locals = locals; }
	response.render(template + '.jade', options);
}

app.listen(3003);
console.log("Listening on port 3003");

Final Thoughts


Again, the code is available in github.

The basic difference from traditional software development was to start with a literal projection of the confirmations that would show the customer and user they got what they wanted. From that I was able drive the functional part of the application by pulling the data and functionality out of the literal templates and into the application.

There are still some rough spots, and I am also relatively new to javascript, node.js and modern web design. My next goal, besides continuing to use the new technique, is to develop some tooling to help with some of the boiler-plate activities.

In the third post in my Neoagility series, I’ll take a step back up a level and look at some of the ramifications of doing software development in this manner.

About these ads