My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

Saturday, February 18, 2012

JavaScript Test Framewors: more than 30 + 1

After @szafranek hint and suggestion, wru landed almost at the end of this Wikipedia List of unit testing frameworks page.

If you use this tweet size hand made imperfect script in the wikipedia page console:

a=q="querySelectorAll",[].map.call(document[q](".mw-headline,.wikitable"),function(v,i){i%2?a=v.textContent:o[a]=v[q]("tr").length},o={}),o

You gonna see that JavaScript is third when it comes to number of test frameworks ... and not sure that's good, anyway, here a quick description of mine.

About wru

You can find most info in github page itself but essentially wru is a generic purpose, environment agnostic, JavaScript tests framework compatible with both client and server, where client means every bloody browser, and server means Rhino, node.js, and recently phantom.js too.
To be really fair, wru is not exactly a unit test framework since it does not provide by default anything related to mocks or stubs.
However, wru is so tiny and unobtrusive that any 3rd parts library could be integrated without effort in whatever test you want + need.

wru In A Nutshell

Well, the first thing to do with wru is to write code such:

wru.test([
{
name: "the test name, for feedback purpose",
setup: function (obj) {
// the OPTIONAL setup property performed before the test
// obj will be a freshly new created object
// with a single test lifecycle ... reusable within the test
},
test: function (obj) {
// obj comes from setup, if any
wru.assert(true); // a generic truish condition
setTimeout(wru.async(function () {
// a generic asynchronous condition
// where you will inevitably assert something
wru.assert("this is also true", true);
}), 1000);

// the handy possibility to reuse assert itself
if (wru.assert("this is a truish value", obj)) {
// do stuff with obj
// or simply log the object
wru.log(obj);
}
},
teardown: function (obj) {
// the OPTIONAL teardown property performed after the test
// obj comes from the test function, is the same
// passed through setup, if present, and test
}
}
]);

After this you are basically ready to go.
Both assert() and async() accept one up to two arguments: the optional description of the test, recommended if you want at least understand what's going on and where if something failed, and the second parameter which is returned in both cases.
Via assert() the condition/expression is evaluated as it is, truish or falsy, and returned if addressed.
Via async() the callback is wrapped in an unobtrusive way and this is simply to let wru know that something is going to happen, hopefully in a reasonable timeout of 10 seconds.

How To See Results

Once you have your test in place, you can re-use the same code all over supported environments.
The cool part about wru is that templates for all scenarios are automagically created at build time.
You don't even need to npm install wru to use the test, or include it in a page via script tag, you can simply grab a template and copy and paste or replace, during a build process, the test. This will make your life easier than any setup oriented test framework, isn't it?

Write Once, Run Everywhere

Isn't this the dream we all have about JavaScript in both browsers and server side environments? As far as I know wru is the only one that supports all these environments with a unified, cross platform, and easy to remember API. (until I have discovered JS Class via @jcoglan)
Main principles behind wru? KISS and YAGNI, so that everyone can start, at least, writing tests for what's needed, without any ulterior waste of time about environment, library dependency, or programming language setup.
And what if you want to test platform specific gotchas ? Oh well, you can still recycle the whole test and check the number of positive results, as expected, at the end ... well, not all the code we are writing should work cross platform, but even in this case, wru gonna make you familiar with tests and tests logic so it's a win win in any case.

Pluggable UT Libraries

You must admit the name of this framework is absolutely crap. I didn't even know how to call it in a manner that no other frameworks would have been affected, so I sticked with my blog initials, WebReflection, and the one out of Unit in it, until I introduced this tiny library in one of those amazing BerlinJS events where someone suggested Where Are You acronym, and I simply loved it ... uh wait, let's go back in the topic ...
Any external library able to mock or stub your code should be easy to integrate in a wru test without problems.
In this case you may need to include this library upfront via script tag, in the Web html template, or inside any server side related template through require() or some other mechanism.
For browsers, you may consider JSMock, as example, but others which aim is to provide mocks or stubs functionality should be all supported without problems.

About Asynchronous Logic

Let's face reality, asynchronous tests are created exclusively to test the condition after the asynchronous callback has been executed, and this is exactly the wru expectation, you call for async? you wait for async.
If you get how async work you'll realize that you don't have to expect() anything, simply do what your test should do and trigger the done() at the end.
This comes from one of the most appreciated asynchronous pattern out there, Promises, where you simply wait for a done() to be called.
wru does the same, except the equivalent of done() is assert() which is the trigger.
If you have truly complex asynchronous logic, here a common pattern you might find useful with wru.

wru.test([{
name: "async",
test: function () {
var expectation = wru.async(function (arg) {
wru.assert("everything went better than expected", arg === "OK");
});
// your whatever async logic you need
var xhr = new XMLHttpRequest;
xhr.onreadystatechange = function () {
// called multiple times during xhr lifecycle
if (xhr.readyState === 4) {
// async, inside async
doSomeStuffOutThere(xhr.responseText, function (e) {
expectation(e.result); // hopefully the string "OK"
});
}
};
}
}]);

in few words, you don't need to expect a thing since once you define a single asynchronous callback in your test logic, you can trigger it once everything has been tested and, if that will never happen, the timeout will automatically flag the test as failed.

wru Output

Things are kept simple in this case as well, with the happy exception for the cursor.
I mean, you don't really need to stare at it, but the cursor is a visual indication that everything is not just stuck, but it's simply waiting for things to happen, if ever.
A classic infinite loop or endless generator is able to freeze the lifecycle of our app, and only a visual feedback will be able to tell you the truth since any other method, specially in browsers where tests are showed only once executed, won't be able to give you a thing ... except a crash in the best case scenario.
The cursor may interfere with the normal output but, at least when it comes to server side tests, whatever external tool will be able to remove cursor noise in the log and analyze anything that happened during test execution, from green to red or black, providing you an instant feedback if something went wrong.

Improve As You Go

wru is not a universal answer to JS tests but hopefully a good start point or most likely everything you need if the project, and your tests, are not that complex.
Being that simple, the best thing about this library is that we could reproduce its API everywhere else in few lines of code, transforming, as example, your tests into another UT framework without much effort.
The fragmentation of JS tests out there is massive, so I don't really want to go deeper into this, just think that this library made asynchronous tests that easy, as well as normal synchronous one and without interferences, thanks to the chosen namespace, out there.
What else about wru ... well, give i a try :)

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.