Using QUnit with CoffeeScript

QUnit is a unit testing framework for JavaScript, from the jQuery project. Earlier this week I wrote about how I chose it over Jasmine for my current project. This article explains how I setup QUnit to test my CoffeeScript, along with the Sinon mocking/stubbing library.

Downloading QUnit

Let's assume that you want to keep your tests in a folder called test/javascripts (relative to your project's directory), and that the CoffeeScript code that you want to test lives in a folder called app/assets/javascripts. This is where the files live in my Rails app; if you're not using Rails just substitute the appropriate paths for your project in the code that follows.

Start by making a directory to store QUnit itself, and then download qunit.js and qunit.css:

$ mkdir -p test/javascripts/qunit
$ cd test/javascripts/qunit
$ curl -O
$ curl -O

We'll also want the in-browser CoffeeScript compiler so that we can compile CoffeeScript within text/coffeescript script tags on the fly. I've stored it in test/javascripts:

$ cd ../
$ curl -O

The test suite HTML file

QUnit needs an HTML file that will load QUnit, our CoffeeScript, and our tests (which are also be written in CoffeeScript). The HTML file needs a few placeholder elements in it that qunit.js uses when rendering the results of your test suite.

The onus is on you to set this file up, but there are plenty of examples online. Here's mine, which I saved in test/javascripts/index.html:

<!DOCTYPE html>
  <meta charset="UTF-8" />
  <title>QUnit Test Suite</title>
  <link rel="stylesheet" href="qunit/qunit.css" type="text/css" media="screen">
  <h1 id="qunit-header">QUnit Tests</h1>
  <h2 id="qunit-banner"></h2>
  <div id="qunit-testrunner-toolbar"></div>
  <h2 id="qunit-userAgent"></h2>
  <ol id="qunit-tests"></ol>
  <div id="qunit-fixture">
  <script type="text/javascript" src="qunit/qunit.js"></script>
  <script type="text/javascript" src="coffee-script.js"></script>
  <script type="text/coffeescript">

You'll notice that I've loaded qunit.js and coffee-script.js.

All we need to do now is to load our tests inside the empty text/coffeescript script tag. To check whether everything is setup properly, let's add a couple of simple tests:

<script type="text/coffeescript">
  test 'true should be truthy', ->   
    ok(true, 'true is not truthy!')

  test 'should be able to append to an array', -> 
    # this test will fail, as this *isn't* how to append to an array
    equal([1, 2], [1] + 2)    

Running your tests

Just open the web page in your browser. We've used relative paths for everything in index.html, so you can load it directly from the file system, with a file:///... URL.

On a Mac? Do this:

$ open test/javascripts/index.html

It should look like this:

QUnit test framework

Loading tests from external files

You could keep all your tests in index.html, but I prefer to store my code in separate files with a .coffee extension and to just use index.html as a way to kick everything off.

I've put this code in my script tag:

<script type="text/coffeescript">
  for file in ['models', 'controllers']                                             
    lib = "../../app/assets/javascripts/#{file}"                          
    load_test = ->                                                                  
      test = "#{file}"                                                  
      -> CoffeeScript.load(test)                                                    
    CoffeeScript.load lib, load_test()  

Let's break that down a bit...

I've used a simple naming convention for my test files. Code in app/assets/javascripts/ is tested by code that lives in test/javascripts/ The for loop is just loading each of my modules, and then (via the callback returned by load_test) loading the corresponding tests. That's all you need to do; QUnit will do the rest.

The CoffeeScript.load function takes a URL and a callback to run after load. I'm loading the tests in a callback to guarantee that the code under test has been loaded before the test code itself is loaded. If you don't do this you can see some test failures when your tests run before the code they're testing gets loaded.

If you're wondering why we need the load_test function (rather than just passing an anonymous function as our callback), it's currying the value of the test variable. If you don't curry it the current value of file is used when the callback executes, rather than the value that it was set to when the callback is defined. So if you don't curry it, most of your test files won't get loaded.

Loading third party libraries

I use MooTools and Serenade.js in my project, so I've added these lines to the list of script tags:

<script type="text/javascript" src="../../vendor/assets/javascripts/mootools-core-1.4.4.js"></script>
<script type="text/javascript" src="../../vendor/assets/javascripts/serenade.js"></script>

You can obviously load jQuery, YUI, etc. in the same way.

Adding a stubbing/mocking library

Unit testing gets painful when you can't stub or mock the objects that interact with the code that's under test. I didn't spend a lot of time investigating the options, but as I read the Sinon docs I took quite a shine to it. I noticed that Serande.js uses Sinon, and that's a good enough recommendation for me. There's also a QUnit plugin which makes it "just work".

To install Sinon download both sinon-qunit and Sinon.JS from the sinon-qunit page and put them in your test/javascripts/qunit folder.

Then add these tags to index.html (but make sure you update the version numbers):

<script type="text/javascript" src="qunit/sinon-1.3.2.js"></script>
<script type="text/javascript" src="qunit/sinon-qunit-1.0.0.js"></script>

Re-running the tests automatically

So that's all fairly simple, but to run the tests you have to switch to your browser and reload the page. That's a bit painful; wouldn't it be good if your test suite ran immediately when you save a .coffee file? I'm now using guard-livereload to reload the page in the browser on save.

I think we've dealt with enough for one article, so I'll show you how to set guard-livereload up for QUnit (with Rails) in my next post...

I love feedback and questions — please get in touch on Twitter or leave a comment.