Testing with mocha and casperjs

When talking about Unit Test in node, mocha flashes first in my brain.

mocha && should.js

Mocha is a feature-rich JavaScript test framework running on node.js and the browser.

I used to use mocha for the elegant way it does async along with familiar BDD style syntax. And I worked mocha with should.js, which is a human assertion library.

Install

$ npm install mocha -g 
$ npm install should --save-dev

First Step

Let's say we have two api to test, and the api script is saved as api.js.

var Add_sync = exports.Add_sync = function () {};
var Add_async = exports.Add_async = function () {};
Add_sync.prototype.add = function (a, b) { return a + b; };
Add_async.prototype.add = function (a, b, cb) {
    setTimeout(function () {
        cb(a + b);
    }, 100);
}

Now we can check these two api's behavior right or not, save it as test/test.js.

var should = require('should');
var add_sync = require('../api.js').Add_sync;
var add_async = require('../api.js').Add_async;
describe('test api', function () {
    describe('sync add api', function () {
        it('should return 3', function () {
            var adder = new add_sync();
            adder.add(1, 2).should.equal(3);
        });
    });

    describe('async add api', function () {
        it('should callback with 3', function (done) {
            var adder = new add_async();
            adder.add(1, 2, function (res) {
                res.should.equal(3);
                done();
            });
        });
    });
});

Pay attention to testing asynchronous code that you should add a callback(usually named done) to it(). Mocha will know that it should wait for completion.

Besides, Mocha supports all hooks that is before(), after(), beforeEach(), afterEach(). We use within describe() at any nesting level, and use outside describe() for global scope.

Running the tests

By default mocha(1) will use the pattern ./test/*.js, so it's usually a good place to put your tests.

However, I prefer to set up a Makefile in the root of the project.

test:
    @./node_modules/.bin/mocha \
        --require should \
        --reporter dot \
        --bail \

.PHONY: test

Then we can just run our tests with $ make test. There are tons of options, visit mocha for more information.

Well, when refering to frontend tests, we focus more on Functional Tests. Sometimes we may just refresh-click-click-click and the testing is done. But how to do with some hudge javascript code on user interface or hundreds of refreshes and clicks? Hence, we still need something to automate test.

casper.js && phantomjs

Even though mocha also support browser, CasperJS is my favorite.

CasperJS, a toolkit on top of PhantomJS.

So let's begin with PhantomJS.

PhantomJS is a headless WebKit with Javascript API.

So what's headless? You most likely know what a browser is, now take away the GUI, that's called a headless browser. You can think of it as a programmable browser for the terminal. It's really cool!

And PhantomJS has fast and native support for various web standards like DOM handling, CSS selectors, JSON, Canvas and SVG. For me, I code with PhantomJS just like code with node!

However, PhantomJS itself is not a test framework, it is only used to launch the tests via a suitable test runner. Hence, CasperJS comes.

CasperJS is an open source navigation scripting and testing utility based on PhantomJS, also support SlimerJS(Gecko). So PhantomJS provides the headless browser capability, and CasperJS fills in the rest: it runs on top of Phantom with built in testing functionality allows for navigation scripting. Let's begin our frontend test!

Install on OSX

$ brew install phantomjs
$ brew install casperjs --devel

As CasperJS 1.1 branch is now pretty stable and supports testing utility, I choose this version to start.

Casper Test API

Now let's open up the /go/programmer subnode on v2ex, click the first topic link and confirm that we do the right operation.

casper.options.viewportSize = {width: 1024, height: 768};
var testCount = 2;
casper.test.begin("Testing V2EX", testCount, function suite (test) {
    casper.start("http://www.v2ex.com/go/programmer", function() {
        test.assertTitleMatch(/程序员/, "Title is what we'd expect");
        casper.click("a[href*='/t/']");
        casper.waitForUrl(/\/t\/*/, function() {
            test.assertExists(".topic_content", "Find the content");
            casper.capture("v2ex.png");  
        });
    }).run(function() {
        test.done();
    });
});

Here, after we click the first topic link, we wait for the url to change and the content of the first topic page to load. And capture the whole page and saved as v2ex.png. Actually, you can also wrap your follow-up codes in a then() callback in order to see the changes from your click event.

casper.then(function() {
    test.assertExists(".topic_content", "Find the content");
    casper.capture("v2ex2.png");
});

The then() callback staff is an implementation of the Promise Pattern. Hence, after calling other casper api like fill() and sendKeys(), you should wrap your next interaction in casper.then().

The fill() api is a convenient way to fill out and submit the form on web page.

casper.options.viewportSize = {width: 1024, height: 768};
var testCount = 3;
casper.test.begin("Searching V2EX", testCount, function suite(test) {
    casper.start("http://www.v2ex.com/", function() {
        casper.fill("form", {
            "q": "casper"
        }, false);
        casper.then(function () {
            this.evaluate(function () {
                dispatch(); // manually submit the form
            });
            casper.waitForPopup(/google\.com/, function () {
                this.test.assertEquals(this.popups.length, 1);
            })
            casper.withPopup(/google\.com/, function() {
                this.capture("search.png");
                test.assertTitleMatch(/Google/, "Title is right!");
                var value = this.evaluate(function () {
                    // we are in DOM!
                    return document.querySelector('input[name="q"]').value;
                });
                test.assertEquals(value, 'site:v2ex.com/t casper');
            });
        });
    }).run(function() {
        test.done();
    });
});

Here, as CasperJS can't submit the search form on v2ex, we should manually submit the form by call dispatch() function after fill the search input. We use evaluate() to run code in the current page DOM context(we are in DOM!). Then v2ex will popup a google hacking result for your search keywords, like site:v2ex.com/t casper here. So there we need waitForPopup and withPopup.

Actually, CasperJS has a fucking awesome doc that I strongly recommend you to read about it. You may need it when you stuck.

Summary

Unit tests are written from the developer’s perspective, and typically target a method or a class. While Functional tests are written from the user’s perspective, and usually test the interaction between multiple building blocks of the application. Your application would be better with both a comprehensive suite of Unit tests as well as a bunch of Functional tests.

Unit testing makes sure you are using quality ingredients. Functional testing makes sure your application doesn't taste like crap.

End

If you have something to correct, welcome to point it out:D

References: