Friday, January 11, 2008

Testing Ajax calls in Prototype

If you're doing any kind of unit testing of Ajax behavior, you'll most likely come upon one minor annoyance. If you test your UI expectations in the context that fires the request, the callback won't have completed yet, and the elements you're setting or creating won't be set up. On the other hand, if you test your expectation in the context that receives the response, the test runner thread will have ended long ago, and any failures won't be caught and reported.

What you really want to do is disable asynchronous callback behavior for your test runs, but unfortunately there is no global configuration parameter for Prototype that controls that.

A little metaprogramming to the rescue.

Simply add the following to the top of your JSSpec test runner to override asynchronous behavior for your tests:

(function () {
var $$setOptions = Ajax.Base.prototype.setOptions;
Ajax.Base.prototype.setOptions = function (options) {
$$setOptions.call(this, options);
this.options.asynchronous = false;
};
})();


(s/setOptions/initialize/g if you're using the latest and greatest Prototype 1.6.)

Just fire the events that cause Ajax requests and test your expectations afterward.

One more thing:

If your Ajax response contains <script> elements, Prototype will kindly extract and execute them for you, provided that you pass evalScripts: true as one of your Ajax options. However, what you may not expect is that it executes them in a deferred callback using the browser's setTimeout call. I initially set out to patch Element.Methods.update, which handles the deferred evaluation behavior, in much the same way as I did setOptions, when it occurred to me that a more general solution would be more helpful for testing. Why not just patch setTimeout and setInterval so that they behave synchronously?

window.setTimeout = window.setInterval = function (f, timeout) {
typeof f === 'string'? eval(f): f();
}


(Again, do this in your test harness, not in your production code!)

Obviously, you're going to completely lose your desired timing behaviors, but if you're trying to test elaborate timing schemes in JSSpec or JSUnit, you're pretty much screwed anyway. But let me know if you find any neat tricks to accomplish that.

Labels: , ,

Wednesday, January 9, 2008

Advanced JavaScript: Not for the Faint of Heart

Thanks to the kind folks from Chicago Perl Mongers, who took two hours out of their busy lives last night to learn a little bit about the dark corners and esoterica of JavaScript.

Those who have only been exposed to procedural and class-based languages often find JavaScript's unique brand of functional programming rather unusual, if not downright confusing. Its lexical scoping and prototype chaining rules allow for some very powerful metaprogramming facilities; but the language is finicky, and the very features that provide so much power can also be badly abused and hacked. It seems fair to say that this has been the rule, rather than the exception, until the dawn of the Ajax revolution in 2005. Did you know that you can easily create arbitrarily deep inheritance hierarchies in JavaScript, even though the language has no concept of classes? Create higher-order functions that can bind or curry parameters to existing functions? Extend language and DOM data types with custom functionality?

The future seems a lot brighter now, thanks to the tireless work of folks like Douglas Crockford, Dean Edwards, and John Resig, and to cross-browser JavaScript libraries like Prototype, jQuery, and Base.

The JavaScript Renaissance, Part I: The Core Language
Presentation
Snippets

Labels: ,