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