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.

Posted Oct 18, 2011 Tweet / Follow me on Twitter

Tom MacWright

I'm . I work on tools for creativity at Mapbox. This is where I write about technology and everything else.