MetaSkills.net
Coding things under other things!

Rails & Spine.JS - Jasmine Testing Part 1

In my previous article I talked a little bit about why I decided to use Spine.JS and how to include the CoffeeScript source into your Rails project using git submodules. Now I would like to talk about testing your brand new Spine.JS application. Afterward, be sure to read the second part to this article which covers more advanced aspects of your Spine.JS application specs.

Testing JavaScript

OK, so like any good programmer, you want to test your JavaScript web application, but how? Like most, I kept finding that Jasmine was the de facto testing framework that most Rails developers were using. For the newly aquatinted, Jasmine describes itself as behavior-driven and sports a clean spec style using describe and it blocks similar to RSpec or MiniTest::Spec. But maybe you, like me, quickly dismissed Jasmine since you sure as hell were not going to hit refresh or F5 in your browser every time you wanted to run your damn specs. After all, this is 2012 and Rails developers do not test with a browser! So why should I start now?

Luckily I am a big fan of Ruby's Guard gem, a simple library that responds to file system events. The guard project literally has TONS of other guard gems that automate everything from running test files to restarting your development server. Thankfully my search for JavaScript testing with Guard in mind brought me right back to Jasmine. Enter the guard-jasmine gem and the wonderful world of a headless JavaScript testing piped right down to your terminal window!

Guard, Jasmine & Jasminerice

So this is our holy trinity and to be honest, there are a lot of moving parts under the stack. Things will seem to get complicated quick, but don't worry. I will give you a brief overview of the moving parts and then get right down to the basics of how you can start using Guard and Jasmine to test your Sine.JS application.

First, let's cover Guard. It is a simple gem that uses a Guardfile at the root of your project to control how other guards are triggered. I'll give you an example Guardfile later. But for starters, read the documentation on what special libraries may be needed for file system events or notifications on your specific platform. In my case, I us Mac OS X and purchased the latest Growl 1.3. So my example Gemfile below will have the ruby_gntp gem included in the spec.

Next up is the guard-jasmine gem. My instructions assume you are running a Rails 3.1 or 3.2 app and that you are taking full advantage of the asset pipeline and CoffeeScript. Many of these details can be found on the guard-jamine's Rails 3.1 setup section of their readme page. The underlying components of guard-jasmine are two a libs named Jasminerice and PhantomJS. Jasminerice is a simple Rails engine that brings in the Jasmine source files to the asset pipeline while running a rack app mounted to /jasmine to run your specs from your current application. PhantomJS is yet another headless WebKit based on Qt that has a rich JavaScript API which guard-jasmine delegates to. Is your head spinning? Mine was too.

Put It All Together

Here is the bullet train to getting this stack up and running. First, you will need to get PhantomJS installed. If Homebrew is your thing, just do $ brew install phantomjs. Or you can download one of their precompiled binaries for your specific platform. This is what I opted to do and I just placed the phantomjs in my PATH somewhere.

Next, we need to get the gems in our Gemfile. Here is how mine are setup. I have them in both the :develpment and :test groups since Jasminerice runs in both of those Rails environments. I also have that ruby_gntp dependency since I am using Growl on Mac OS X, YMMV.

group :development, :test do
  gem 'jasminerice'
  gem 'guard-jasmine'
  gem 'ruby_gntp'
end

So that was easy, now on to our Guardfile. Here is mine below. Notice how I put my JavaScript related guards into a js group? This is a seldom used feature of Guard and it means I can monitor only my jasmine specs by starting guard off using $ guard -g js and my other guards in my ruby group, like minitest, will be ignored.

group 'js' do

  guard 'jasmine' do
    watch(%r{app/assets/javascripts/myapp/index\.js\.coffee$})  { "spec/javascripts" }
    watch(%r{app/assets/javascripts/myapp/(.+)\.js\.coffee$})   { |m| "spec/javascripts/#{m[1]}_spec.js.coffee" }
    watch(%r{spec/javascripts/(.+)_spec\.js\.coffee$})          { |m| "spec/javascripts/#{m[1]}_spec.js.coffee" }
    watch(%r{spec/javascripts/spec\.js$})                       { "spec/javascripts" }
    watch(%r{spec/javascripts/spec_helper\.js$})                { "spec/javascripts" }
    watch(%r{spec/javascripts/jasmine-myapp.*})                 { "spec/javascripts" }
  end

end

This setup assumes a few things. First that you are only testing your Spine.JS application and that those files are in the app/assets/javascripts/myapp directory. That myapp directory could just be app in your case if you used the spine-rails gem without the --app option. In my case, that folder is named homemarks. This Guardfile also assumes that your JavaScript app and specs are CoffeeScript files and that specs are in the spec/javascripts folder specified by Jasminerice. You are going to want to follow some file naming convention now too. For example if you have a Spine.JS app file in app/assets/javascripts/myapp/models/post.js.coffee, then you are going to want the matching spec in spec/javascripts/models/post_spec.js.coffee. So saving each of those files will trigger that specific spec to run. There are also so some watchers on important root files like your spec.js sprockets manifest and your Spine.JS app index. Changing any of those files will result in your entire spec suite running again.

To Be Continued...

I will go into more details on the spec_helper.js and jasmine-myapp files above in the second part of this article. For now you should be set to start writing specs like the one below and seeing them run by either visiting the /jasmine URL of your running Rails application or by using Guard in your terminal window.

describe 'App', ->

  it 'sets el', ->
    expect(MyApp.Application.el).toEqual $('#app')

  it 'sets the userId as a property on itself', ->
    expect(MyApp.Application.userId).toEqual @bob.id

Continue to part two...

Related

Resources