2.12.2012

Nomination part 5 - testing



Version en espaƱol


This post is part of the series about creating an app with node.js, express for Facebook





So lets not code everything without knowing if we are doing things right or what... lets start testing what we do or better yet lets try to do some TDD, or do the test cases before the actual code... for this part we need to install some modules to test, first one is vows which has a nice way to describe the test cases, zombie to browser without a browser :) and jsdom to manipulate the dom once the pages are loaded, "up an atom"...


Lets update our package.json adding vows, zombie and jsdom, it will look like this:

{
    "name": "nomi-nation"
  , "version": "0.0.1"
  , "private": true
  , "dependencies": {
      "express": "2.5.2"
    , "express-messages" : ">= 0.0.2"
    , "facebook-js" : "1.0.1"
    , "jade": ">= 0.0.1"
    , "log" : "1.2.0"
    , "mongoose" : "2.4.8"
    , "vows" : "0.6.1"
    , "zombie" : "0.12.9"
    , "jsdom" : "0.2.10"
  }
}

And then we install the new dependencies with:

npm install -d

Now that we have our modules, lets create a folder called "test" or whatever and we create 3 files, "main.js", "controller.js" and "routes.js".

Lets start with the controller, lets imagine we have this structure in our db:
3 nomination, test1 owner "1", test2 owner "2" y test3 owner "3"
Easy right, lets start our testing, first the controller code will be like this:

/**
 *
 * Controller testing
 *
*/
var vows = require('vows'),
    assert = require('assert'),
    nominator = require('../controllers/nominator.js');

exports.findN = vows.describe('find nominations').addBatch({
    'when finding where 2 voted' : {
        topic : function(){
            nominator.findVoted(2,this.callback);
        },
        'result its only one nomination' : function(err, docs){
            if (err) { console.log(err); return; }
            assert.lengthOf(docs, 1);
        },
        'and its test2' : function(err, docs){
            if (err) { console.log(err); return; }
            assert.equal('test2', docs[0].name);
        }
    },
    'when finding where 1 voted' : {
        topic : function(){
            nominator.findVoted(1,this.callback);
        },
        'result is 3 nominations' : function(err, docs){
            if (err) { console.log(err); return; }
            assert.lengthOf(docs, 3);
        },
        'and are test1 2 and 3' : function(err, docs){
            if (err) { console.log(err); return; }
            for (var i=0;i<3;i++){
                assert.equal('test'+(i+1), docs[i].name);
            }
        }
    },
    'when finding where 8 voted' : {
        topic : function(){
            nominator.findVoted(8,this.callback);
        },
        'result is 0' : function(err, docs){
            if (err) { console.log(err); return; }
            assert.lengthOf(docs, 0);
        }
    }
}).addBatch({
    'when finding 1 nominations' : {
        topic : function(){
            nominator.findMyNominations(1,this.callback);
        },
        'result its only one nomination' : function(err, docs){
            if (err) { console.log(err); return; }
            assert.lengthOf(docs, 1);
        },
        'and its test1' : function(err, docs){
            if (err) { console.log(err); return; }
            assert.equal('test1', docs[0].name);
        }
    },
    'when finding 9 nominations' : {
        topic : function(){
            nominator.findMyNominations(9,this.callback);
        },
        'result is 0' : function(err, docs){
            if (err) { console.log(err); return; }
            assert.lengthOf(docs, 0);
        }
    }
}).addBatch({
    'when finding where 8 is nominated' : {
        topic : function(){
            nominator.findNominated(8,this.callback);
        },
        'result its 3 nominations' : function(err, docs){
            if (err) { console.log(err); return; }
            assert.lengthOf(docs, 3);
        },
        'and its test1, 2 and 3' : function(err, docs){
            if (err) { console.log(err); return; }
            for (var i=0;i<3;i++){
                assert.equal('test'+(i+1), docs[i].name);
            }
        }
    },
    'when finding where 1 is nominated' : {
        topic : function(){
            nominator.findNominated(1,this.callback);
        },
        'result is 0 ' : function(err, docs){
            if (err) { console.log(err); return; }
            assert.lengthOf(docs, 0);
        }
    }
});

Line 1-8: Loading modules and our controller "nominator.js"


10: we export the function "findN" with exports and we start describing the test in this case we are going to name it 'find nominations' and we add a batch, this will hold all our test cases.


11-14: The first test is called 'when finding where 2 voted', eso which means we are going to search for nominations where the user with id "2" already voted, first we create the topic that is like the entrance to the test case where we call the function "findVoted" from the nominator controller, we send the id to search and something important here, we use the local callback "this.callback", this means that whatever the function returns it will go to all the next test we have inside this topic in an async way


15-18: The next test is: 'result its only one nomination', the result expected its only one nomination and to check it first lets make sure we didnt get any error from the callback and then the lenght of the docs returned, since we are searching this always return an array of docs, even if the result is only one, to check the lenght we do "assert.lengthOf" to docs and we compare to 1 that is the lenght we are expecting.


19-22: Next test its called 'and its test2', here we also get the results from "findVoted" and in this case we check that the name of the nomination is "test 2" which is the only one that user 2 has voted, first we check for an error and then we check the name asserting "doc[0].name" to "test2"

...: Well its the same again and again in this file just checking different functions from our nomination controller and checking other things, we just add them to the batch, review them, any questions let me know in the comments


This code is what i use to add users and nominations:

var nomination = {
    'name' : "test3",
    'owner' : "3", //who is the owner of the nomination
    'endDate' : new Date(), //when this nomination is going to end
    'category' : "cat1",
    'sub_cat' : "sub1",
    'active' : true //nomination finished
};
nominator.createNomination(nomination, function(err, doc){
    if (err) { console.log(err); return; }
    var users = [];
    for(var i = 4; i<12; i++ ){
        users.push({
            "_id" : i,
            "name" : i,
            "votes" : 0
        });
    }
    nominator.addUser(doc, users, function(err){
        if (err) {  console.log(err); return; }
        var user = {"_id":9, "name":9, "votes":0 };
        nominator.eraseUser(doc, user, function(err){
            if (err) {  console.log(err); return; }
            nominator.addUser(doc, user, function(err){
                if (err) {  console.log(err); return; }
            });
            nominator.addUser(doc, {"_id":19, "name":19, "votes":0 }, function(err){
                if (err) {  console.log(err); return; }
            });
        });
    });
});

In this case we are creating a nomination and after that we add some users with "adduser" and then we erase some and add more just for fun.

And to add votes we do:

nominator.findMyNominations(1, function(err, doc){
    if (err) { console.log(err); return; }
    for (var i=2;i<5;i++){
        nominator.vote(doc[0], 1, i, function(err, doc){
            if (err) {  console.log(err); return; }
        });
    }
});

We search for userid 1 nomination and we add voters. try to do some testing for this with vows...

Good, lets test our main route "/", zombie to the rescue. Lets add the following code to "routes.js":

/**
 *
 * Test index route
*/
var vows = require('vows'),
    assert = require('assert'),
    zombie = require('zombie'),
    jsdom = require('jsdom');

exports.index = vows.describe('Visiting index').addBatch({
    'when hitting the page': {
        topic: function(){
            zombie.visit('http://localhost:3000',this.callback);
        },
        'we got the page': function(err, browser, status) {
            if (err){
                throw(err.message);
            }
            assert.equal(status, 200);
        },
        'and try to get the title' : {
            topic : function(browser){
                var vow = this;
                jsdom.env({
                    html: browser.response[2],
                    scripts: [
                        'http://code.jquery.com/jquery-1.5.min.js'
                    ],
                    done: vow.callback
                });
            },
            'the title is Express' : function(errors, window) {
                //console.log(errors);
                if (errors){
                    throw(errors);
                }else{
                    var $ = window.$;
                    assert.equal('Express', $('title').html());
                }
            }
        }
    }
});

Line 1-8: loading modules

10: We add our test "visitin index" y we add our batch (set) of testcases

11-14: "when hitting the page" this test will fire zombie and will try to reach our page, as a callback we put our local callback so all the other test cases have the results

15-20: 'we got the page', this page takes the arguments error, browser and the status, first we check for errors and then that we have a 200 response

21-22: 'and try to get the title', here we are adding a new topic in this case we take the response from zombie minus the error and also this one have status but im not adding it

23-30: lets store vows in a var for future reference and we use jsdom, in  this case we are sending the html that we got from zombie, we inject it jquery and we call the local callback for the next test case

32-39: 'the title is Express', this test gets for arguments what jsdom returns that it may be a possible error or the window that holds all the page, its like our browser page, we check for page errors and if everything is good with jquery we check for the title as if you were in the browser.

Cooll, lets create or "main.js" to call our testing

#!/usr/bin/env node
/**
 * Test runner
 *
*/
var controllers = require('./controllers');
controllers.findN.run();

We just got our testing file and we run them, check the documentation for more great stuff

No comments:

Post a Comment