When Sammy Met Jasmine
Testing Sammy JavaScript apps with Pivotal Labs’ Jasmine BDD framework
Aaron Quint’s route-driven JavaScript framework Sammy, ships with QUnit tests. This is an obvious choice, since QUnit is jQuery’s test framework and Sammy depends on jQuery. I, however, am not the biggest fan of QUnit. It works really well, but I find the syntax a little unwieldy. On a recent project, I thought I’d try out Jasmine – a library-independent JavaScript BDD framework with a more readable syntax.
In this post I’ll run through some typical Sammy scenarios and show you how I’ve written the corresponding Jasmine tests. For reference, I’m using Jasmine 0.11.1 and Sammy 0.5.4.
Setting up Sammy
Let’s start with a fairly simple Sammy app with some common features.
At the top of the app, the context
variable is assigned, providing easy and unambiguous access to the Sammy.Application
instance itself, for use elsewhere. A GET route is declared, which simply triggers an event named myEvent
. The second argument to trigger
passes an object to the event:
Here is the event that was triggered in the GET route. Bound events accept a custom object as their second argument, which is used in this example to pass in some arbitrary values:
Here, a (very) rough example of a modular design pattern is established for any non-event driven parts of the app. I like to group functions by similarity of purpose.
An aside: Can I read all this shit easily?
I’ve started to ask myself this question frequently. If the answer is no, it’s time to abstract.
Particularly where jQuery is involved, I often find code that looks similar to this:
It only takes a few lines of jQuery, chained to the hilt, to achieve a lot. This is seen as an advantage, and rightly so for small pieces of UI scripting, but it’s a huge pain when testing larger applications since it leads to bloated specs. Imagine trying to test the above example – a long list of calls needs a long list of assertions. But testing needn’t be a chore! Whichever way a JavaScript project is structured, from fully modular and enclosed to fly-open-for-everyone-to-see, keeping functions succinct and limited in purpose makes for much easier testing. Readable code == readable tests == clear statement of intentions to leave for future developers == painless development.
Testing with Jasmine
Here’s an initial attempt at testing the above Sammy app using Jasmine. The specs are within describe
blocks. These aren’t strictly necessary but nesting them helps to describe your tests in relation to the structure of your app. See how the nesting follows the namespacing of the url.build
function, for example:
Since the app involves DOM insertion, it’s clear that some kind of setup and teardown of temporary DOM elements is required. Using Jasmine’s beforeEach
method, the relevant elements are inserted into the DOM before each spec is run. Since each spec within this describe
block refers to the same event, the event trigger is here as well, to avoid repetition. The DOM elements are cleaned up using afterEach
.
Specs are enclosed in the it
blocks and assertions/expectations are made using expect
. The syntax, when read out loud, sounds pretty close to how I’d describe my tests. “It should etc.”; “Expect actual to match expression”. Jasmine has various matcher methods such as toMatch
.
Befriending the DOM
There’s something a little clunky about certain aspects of the above tests. In particular the DOM-related areas look a little hacky. It would be unfair to call this a fault of the testing framework – DOM manipulation isn’t even on the Jasmine agenda. So this is where velesin’s jasmine-jquery extensions step in.
jasmine-jquery allows for various improvements on the original tests:
Firstly, the manual setup and teardown is no longer required, thanks to loadFixtures
, which loads in the necessary DOM elements from an external file, inserts them into a wrapper div inside the document body, and cleans up automatically after each spec is run. Big win. People who are really smart might even try to load fixtures in from the same files as partials to avoid duplication (I have a feeling this could all come together beautifully with Sammy’s new Meld feature…). An alternative is to use setFixtures
, which accepts either an HTML string or a jQuery element as its only argument rather than loading from an external file.
jasmine-jquery also provides a set of custom matchers. Here, for example, there’s no need for the workaround of checking the innerHtml of an element against a regular expression, when toHaveText
does the trick.
Conclusions
The Jasmine documentation could really do with a spruce up – especially in the less immediately obvious areas like spying, mocking and stubbing. It’s possible to bundle through and get some pretty nifty async tests running (blog post on this topic pending) but clearer instructions would be a big help. I’m sure better docs are in the pipeline with version 1.0.0 proper due to be released soon.
I find testing with Jasmine a more pleasant experience than any I’ve had with another JavaScript testing framework. The only thing that has come close for me is JSpec, which has very similar syntax to Jasmine if you eschew the custom Rubyesque grammar.
Provided you structure your Sammy application in a sensible way, it’s easily testable with Jasmine. I’d like a way of testing the Sammy routes themselves, something akin to functional testing as apposed to unit testing. But I’m happy enough with relying on the Sammy internal tests and keeping the routes as simple trigger calls.