« Running the 118th Boston Marathon | Main | Target your recruiting »

JavaScript testing for Rails 3.2 by integrating Jasmine

I wanted to write unit tests for JavaScript components of the Rails application I’ve been working on recently, and intended to use the Jasmine framework to do it. However, I found a lot of conflicting information about what the current incantations are for making this work properly, and a near-total lack of documentation for my particular setup. Consequently, I’m documenting my process here, in hopes that it will help someone else.

The application uses Rails 3.2 (actually 3.2.13 at the moment). I made a false start using the jasmine-rails gem, but in the end I had two big problems with that: the rake task which was supposed to run the tests couldn’t find the Jasmine library, and while Rails was able to mount the Jasmine engine, it wasn’t loading the scripts I intended to test.

I dumped that branch and went back to master and started over. Following the tip on the Jasmine page linked above, this time I tried using jasmine-gem. (It was v1.3.0.) I installed it as directed on the gem’s README page, specifically by adding gem 'jasmine' to the project’s Gemfile, running bundle install, and then rails g jasmine:install and rails g jasmine:examples.

(In the end I didn’t commit any of the code generated by the jasmine:examples generator, but it was illuminating.)

The gem then suggests starting a server with rake jasmine, and sure enough, that worked. It took a bit to get the server seeing the code to be tested, however. This involved tweaking the spec/javascripts/support/jasmine.yml file to include the proper set of scripts. Specifically:

  • The src_dir value needed to be public/assets. This Stack Overflow question pointed me in the right direction. I had to add a manifest file (i.e. assets/application.js) to the src_files array as well in order to include the script containing the code I wanted to test.
  • I needed jQuery to be available to Jasmine. jasmine-jquery turned out to be the solution here, not because it itself is useful (although it is) but because it pointed out that I needed to add jQuery to my src_files file. (It isn’t in my manifest because I’m loading it from a CDN.)

Now I had running Jasmine tests. (Once I wrote the tests, of course, within the spec/javascripts directory. More on this at the end.) However, my rspec unit tests for Ruby code run automatically when I change the relevant files (either the code, or the test code which tests it) so I don’t need to kick off a test run to see if I broke something. I use Guard for this. (Currently v1.6.2.) I wanted Guard to run my Jasmine tests as well, so I’d get the same kind of instant feedback for JavaScript that I get for Ruby.

For this I wound up installing guard-jasmine. (This was v1.16.0.) Once that was in my bundle (add to Gemfile, run bundle install, rinse, etc.) I ran guard init jasmine and it added a block to my Guardfile to watch the JavaScript files and Jasmine specs.

Running this was a little trickier, however, because once again I needed to train it to find the code I wanted to test against. This required me to install jasminerice (v0.0.10), even though I don’t need that gem’s primary function. Once I had that in my bundle and had created a spec/javascripts/spec.js.coffee file (essentially a manifest file for my Jasmine specs) and a placeholder spec/javascripts/spec.css file, this worked fine. I had to restart Guard to make sure it found and started a Jasmine server properly, but it was running the specs I’d written previously with the free-standing Jasmine and they were passing.

Now, about that test code:

There was a step 0 to this process which I had previously stumbled on but hadn’t really faced before, which was that in order to test JavaScript, one must have testable JavaScript. A recent article in A List Apart addressed this quite nicely; in short, instead of the usual written-three-lines-at-a-time soup of jQuery functions that normally come along with a Rails application, you want to build real JavaScript objects; then your test code can instantiate those objects and run assertions against their behavior.

Post a comment