TDD, JsTestDriver and YUI

As I mentioned in my previous entry, the goal of my sabbatical is to build a JavaScript Application. Notice the emphasis is on Application. That is, I don’t intend to build a JavaEE web application with plenty of JavaScript. The goal is to build an Application in the browser. It will probably (eventually), communicate with a server side component for persistent storage and synchronization but for now, that’s secondary.

YUI App Framework
Taking the path of least resistance, I’ll start out with YUI. I have used YUI at a client site on and off for a number of years. YUI seems to be a good candidate for what I want to do, especially since, with version 3.4.0, YUI rolled out the beta version of the App Framework which according the documentation is “…a simple MVC-style framework for writing single-page JavaScript applications.” Sounds like what I need.

The Application
The application will be a Shopping List. A Shopping List seems simple enough to get started and yet has enough potential to make interesting in the long run. It would also have a natural desktop component, in the kitchen putting the list together using your laptop, and at the grocery store actually shopping, probably using your Smartphone.

JsTestDriver
Everybody and their dog seems to want to write their own testing framework for JavaScript these days. At least if you want to believe Google Search. YUI Test would have been an obvious candidate since I want to use YUI and I’ve used YUI Test before. However, YUI Test is a browser based framework which, at least for me, did not lend itself to the classic red-green-refactor process I’ve come to appreciate when test driving e.g. Java.
Having read, Christian Johansen’s excellent Test-Driven JavaScript Development a while back I was really interested in trying out JsTestDriver so that’s what I did.

Enough talk. Let’s get started.

Test Driving
Assume we want a class (in the YUI sense) representing a shopping list Item. A first test case using JsTestDriver might look something like this:

(function() {
TestCase("ItemTest", {
"test creating Item instance should create valid instance with properties specified in constructor": function() {
YUI().use('item', function(Y) {
var milk = new Y.Item({name:'milk'});
assertEquals('name should be milk', 'milk', milk.get('name'));
});
}
});
})();

Test Breakdown

  • In the interest of not polluting the global namespace we wrap the test case in an anonymous function that is invoked immediately
  • JsTestDriver requires that test methods start with “test”.
  • We wrap the actual test code in a YUI use call where we specify ‘item’ as a YUI module that we want to use. This is the module we anticipate implementing the class Item in.

Driving
In IntelliJ I added the JsTestDriver plugin and launched the server. I also attached a Safari browser to the server. It should be green and have a browser attached.

I created a jsTestDriver.conf configuration file like this,

server: "http://localhost:9876"

load:
– “http://yui.yahooapis.com/3.4.0/build/yui/yui-min.js”
– src/main/js/*.js
– src/test/js/*.js

and an IntelliJ run configuration like this:

This test will hopefully fail since, not only have we not created the Item class, the item module does not even exist. As expected, this is what we get.



...
[WARN] yui: NOT loaded: item
...

Let’s create the module and Item class in Item.js like this:

YUI.add('item', function(Y) {
Y.Item = Y.Base.create('Item', Y.Model, [], {}, {
ATTRS: {
name: {
value: ''
}
}
});
}, '0.0.1', {requires:['model']});

Running the test again, unfortunately gives basically the same result except that it now can’t seem to find the model module.
...
[WARN] yui: NOT loaded: model
...

This dependency was of course introduced with our item module. The Item class is derived from Model and we explicity added the requires clause specifying model.

YUI Dependencies

The callback that you supply to the YUI().use() method is invoked synchronously if all dependencies are already available on the page. For example, you may have used another module that depends on the model module in which case it and it’s dependencies are already available or you explicitly loaded the modules needed with a script tag. If it’s not available, the callback will be invoked asynchronously. YUI will figure out what modules are needed, load those not already available, and once those have been loaded invoke your callback.
In our case we have only made the YUI seed file available (see jsTestDriver.conf above) which basically only provides the capability to load module dependencies on demand.
This is a problem for JsTestDriver which expects the test to execute right away. My solution was to force YUI to never load dependencies on demand.

Configure the YUI instance
There is a way to force YUI to not try to load missing dependencies using the configuration property: bootstrap. Setting bootstrap to false, means that YUI won’t even try to load anything asynchronously. Let’s change the YUI() call to pass it the configuration like this.

...
YUI(<strong>{bootstrap: false}</strong>).use('item', function(Y) {
...

The YUI Configurator to the Rescue
Now we need to figure out what YUI modules to include given that we depend on the model module. The YUI Configurator is the answer. You pick the module or modules you depend on and it will tell you which source files to include. With some copy/paste and search/replace magic in the jsTestDriver.conf file you’ll end up with something like this.

server: "http://localhost:9876"
load:
- "http://yui.yahooapis.com/3.4.0/build/yui-base/yui-base-min.js"
- "http://yui.yahooapis.com/3.4.0/build/oop/oop-min.js"
- "http://yui.yahooapis.com/3.4.0/build/event-custom-base/event-custom-base-min.js"
- "http://yui.yahooapis.com/3.4.0/build/event-custom-complex/event-custom-complex-min.js"
- "http://yui.yahooapis.com/3.4.0/build/attribute-base/attribute-base-min.js"
- "http://yui.yahooapis.com/3.4.0/build/base-base/base-base-min.js"
- "http://yui.yahooapis.com/3.4.0/build/base-build/base-build-min.js"
- "http://yui.yahooapis.com/3.4.0/build/escape/escape-min.js"
- "http://yui.yahooapis.com/3.4.0/build/json-parse/json-parse-min.js"
- "http://yui.yahooapis.com/3.4.0/build/model/model-min.js"
- src/main/js/*.js
- src/test/js/*.js

 

Lo and behold!
We have a passing test!

Problems
Sometimes JsTestDriver, and/or the IntelliJ plugin get confused and can’t seem to run any tests. Typically these issues are resolved by restarting the JsTestDriver server. The browser will reconnect automagically. Once or twice I’ve had to restart the browser and/or the IDE as well. Only time will tell if issues like these will be too problematic once I get into more advanced test scenarios.

Conclusion
JsTestDriver looks like a workable solution even though having to go through the YUI Configurator is a bit clunky. The really cool thing with this is when you connect your SmartPhone’s browser to the JsTestDriver server and, voila, you run the same tests in the phone’s browser.

There are of course other frameworks worth looking at but I’ll stick with JsTestDriver for now. It has challenges coming up as it is. I need to be able to run these tests in a Continuous Integration environment and it would also be nice to find out what kind of code coverage I have. Supposedly these are supported with JsTestDriver. I’ll try those out next.

Source
The scenario described above with some more tests is available at github.

Stay tuned for more.

4 responses on “TDD, JsTestDriver and YUI

  1. I would recommed you to have a look at backbone.js, a great framework to build browser based applications in javascript. Made me find the good parts of javascript and we have used in in some parts of our web app.

  2. Hi Daniel, your post is very interesting to me because I’m trying to use jsTestDriver with YUI. I am stuck with the YUI Dependencies problem and your temporary solution of specifying all the modules needed beforehand won’t work for my case. Is there a solution to make loading YUI modules on demand work in jsTestDriver?
    Cheers.

    1. Mo,
      I’m sorry, but I haven’t spent any more time on this. Please, feel free to update this thread if you find a solution.
      Thanks,
      /Daniel

Comments are closed.