Client-Side Javascript Testing & happen
TL;DR – I wrote a library called Happen that makes it easier to use the createEvent API to do browser tests that actually use events.
A quick interlude of the land of testing.
For my projects recently, I’ve been using Jasmine for testing Wax and Modest Maps. 1
But the bugs that started hitting me weren’t the bugs I was testing against.
Picky browser bugs as silly as
Internet Explorer’s handling of window.setInterval(function(){}, 0) weren’t
being tested for because all I was testing was the API, not the functioning of the thing.
What I needed was browser events, and I found that there weren’t any options
for getting them, so I wrote a very tiny one: Happen.
Happen lets Wax and Modest Maps do tests with user events. That means full-integration tests – not just testing that Modest Maps can figure out the URLs for tile images. With Happen, I can test that the browser can successfully move a map, reposition tiles, and fire events at the proper time when a user mouses down, drags, and mouseups – and the rest of the things a map should do in response to user input. And not by rewriting the API or wrapping these things any more – by actually providing the code with events that work just like normal events, because they are normal events.
This test is mapping bliss:
it('does not zoom in on single click', function() {
expect(map.getZoom()).toEqual(0);
happen.click(map.parent);
expect(map.getZoom()).toEqual(0);
});
it('zooms in on double click', function() {
expect(map.getZoom()).toEqual(0);
happen.dblclick(map.parent);
expect(map.getZoom()).toEqual(1);
});
The How and Why
The magic under the surface is document.createEvent and the initEvent APIs. They’re rather obtuse: here’s part of Happen’s abstraction code:
evt.initMouseEvent(o.type,
true, // canBubble
true, // cancelable
window, // 'AbstractView'
o.clicks || 0, // click count
o.screenX || 0, // screenX
o.screenY || 0, // screenY
o.clientX || 0, // clientX
o.clientY || 0, // clientY
o.ctrl || 0, // ctrl
o.alt || false, // alt
o.shift || false, // shift
o.meta || false, // meta
o.button || false, // mouse button
null // relatedTarget
);
Other Options
jQuery does have part
of this API: you can call $('#thing').click(), but it operates
on a different level, by attempting to find event listeners and then
triggering them: the freeform jQuery event system is super-useful,
but not something that’s actually in the DOM – it’s very well-orchestrated
magic. What we’re creating here are real events with normal bubbling
and normal default behavior.
So far this combo is what works: Selenium is the only alternative I’ve found that does real-life events, and I think that it solves the wrong problems: is it really that hard to initially write tests and run them in browers? That’s not a problem for smaller test suites – the problem is maintenance and being able to run your tests everywhere, that matters.
So, now I can open up the testing index.html in Modest Maps or
Wax and it just works – no need to install tests on a Windows-running
netbook or an iPad. So far it’s testing bliss. If you’ve got client-side
libraries and loove testing, happen might help you out.