Before, I lamented about the testing story for Coffeescript in Rails. This is a description of a process that I am currently using. It is not perfect (and I'll end on why I do not think so) but it checks about 75% of the boxes on my Want list.

Quick Start

First, I just want to give a quick start guide for anyone that wants to just get shit done. I will dive into some details after this section--so if you're interested in how it works or are debugging something, that part will be of more interest.

First, we're going to create a Rails application.

rails new testing_coffeescript

It will generate output like so

      create  
      create  README.rdoc
      create  Rakefile
...
Using sqlite3 (1.3.5) 
Using uglifier (1.2.3) 

Next, you'll need a couple more gems. Edit your Gemfile to include jasmine and jasminerice. Run bundle install.

group :development do
   gem 'jasmine'
   gem 'jasminerice'
end

The jasmine gem will come with some generators. I actually prefer to NOT use them and 'roll my own' scaffold because its easier to get to work with jasminerice.

mkdir -p spec/javascripts
touch spec/javascripts/spec.js
touch spec/javascripts/spec.css

In both spec.js and spec.css, you will need to use asset pipeline requires for application. You will also require the spec/javascripts directory.

//= require application
//= require_tree .
/*
 *= require application
 */

And now you should be ready to run specs. Start your rails server (rails s) and hit http://localhost:3000/jasmine.

If everything works, you should see "0 specs, 0 failures in 0.001s" as a green bar. Write a sanity spec by doing the following in spec/javascripts/sanity_spec.coffee

describe "sanity", ->
    it "1 should == 1", ->
        expect(1).toBe(1)

Save the file, run the tests by refreshing the browser and now you should see "1 spec, 0 failures". Finally, there is one last step to testing your coffeescript. Lets assume we had this class:

class Seven
    add: (anotherNumber) ->
        7 + anotherNumber

and a test

it "should add 7 to be 14", ->
    expect(new Seven().add(7)).toBe(14)

you may scratch your head at why you will get an error saying ReferenceError: Seven is not defined. Essentially, this is because Seven is not in scope. You have to add a line to your code to make it available for testing:

class Seven
    add: (anotherNumber) ->
        7 + anotherNumber

window.Seven = Seven

And now you're all set to write tests for your coffeescript!

How it Works

Most of the work is being done by the Rails asset pipeline and the jasminerice middleware. The Rails asset pipeline can give us compiled coffeescript on-demand and we do not have to compile it manually. It also does all the includes heavy lifting for us (yay! No having to manage test lists).

The jasminerice middleware uses the Rack API that Rails sits on top of in order to intercept any requests that go to /jasmine and request all the necessary bits and pieces. We actually aren't hitting Rails at all when we hit /jasmine.

/jasmine will request spec/javascripts/spec.js and spec/javascripts/spec.css as the roots for all of the javascript and css that we will use. In spec.js, we require application and the spec library. It may be necessary to include the application before the tests. Jasminerice also helpfully includes jasmine for us and supplies the necessary HTML template. If you define a new root javascript identifier, you will need to also include that in spec.js so you can get the additional coffeescripts.

Continuous Integration

We run these tests in our CI environment using jasminerice, as well. We use phantomjs to run them headlessly. Phantomjs does need a runner file which we've lifted from the guard-jasmine gem (On GitHub). We spin up a rails server (we picked port 5555) and then run the tests against it.

Issues

My issues with this approach are mainly just reproaches against Jasmine itself. I do not like having to refresh a browser to run tests. Something about it just seems wrong. It makes my skin crawl. That being said, it does make me very happy that my process is this streamlined. Just the other day I was able to truly TDD a mostly-javascript widget and it was a joy. So, with that in mind, here are the issues:

  • Running tests by refreshing a browser is cludgy and does not integrate well in the development process.
  • We run tests differently in development than we do for CI. In theory they are the same but we have already run into issues where that was not the case.
  • The output for the phantomjs runner we are using is ugly. This can be readily fixed but just needs some time and elbow grease.
  • Running tests with a DOM hides issues with code that is tightly coupled to the DOM.

And that's pretty much it. It's quite a nice setup otherwise!