How To Get Rails 3 and RSpec 2 Running Specs Fast (From Scratch)

2011-02-27 18:42

How To Get Rails 3 and RSpec 2 Running Specs Fast (From Scratch)

by Peter Cooper

at 2011-02-27 10:42:38

original http://feedproxy.google.com/~r/RubyInside/~3/9kQfKhEconY/how-to-rails-3-and-rspec-2-4336.html

Rails 3 is great. RSpec 2 is great. And Ruby 1.9.2 is really great. Getting them all running together and quickly, however, isn't entirely straightforward. In this post I demonstrate how to get everything ticking over along with automatically running, super-snappy test runs.

The ultimate outcome is using Ruby 1.9.2 (though much of this is relevant to 1.8 still) to create a Rails 3 app, hook up RSpec 2, and be able to run specs quickly. The first two parts are easy(ish) but the "quickly" part requires some tinkering. Grab a coffee and carry on..

Create a new Rails 3 app

Got Rails 3 installed? If not, gem install rails will see you good. Then head on down to your favorite project folder with your shell and create a new Rails 3 app like so:

rails new myapp --skip-test-unit

You can retroactively bring RSpec 2 into an existing Rails 3 project, of course, but it's easier for this walkthrough to start afresh in case of application-specific issues.

Hooking up RSpec 2 with RSpec-Rails

Edit the Gemfile file in your new Rails project (myapp/Gemfile in this example) and add the following block to the bottom:

group :development, :test do
  gem 'rspec-rails'
end

This tells Bundler (a gem management and dependency tool Rails 3 likes to lean on) we want to use the rspec-rails gem which will get RSpec running with Rails 3.0 for us. Next, we get Bundler to do its thing:

bundle

This will install all of the gems referenced in Gemfile, including rspec-rails. (You can use bundle install instead, if you prefer, but bundle on its own works too.)

Last but not least, we need to run RSpec's 'generator' that rspec-rails has put in place for us:

rails generate rspec:install

The generator creates a few files. Namely:

  • .rspec - a config file where we can store extra command line options for the rspec command line tool. By default it contains --colour which turns on colored output from RSpec.
  • spec - a directory that will store all of the various model, controller, view, acceptance and other specs for your app
  • spec/spec_helper.rb - a file that's loaded by every spec (not in any automatic way but most have require 'spec_helper' at the top). It sets the test environment, contains app level RSpec configuration items, loads support files, and more.

We still can't run rake and see anything interesting yet because we don't have a database or any models initialized.

Creating a model to test

Let's take the easy way out and use the scaffold generator to flesh out something for us to test (as well as to see what spec files can be generated automatically):

rails generate scaffold Person name:string age:integer zipcode:string

It's worth noting that when you generate the scaffold numerous spec files are also created (thanks to rspec-rails):

spec/models/person_spec.rb
spec/controllers/people_controller_spec.rb
spec/views/people/edit.html.erb_spec.rb
spec/views/people/index.html.erb_spec.rb
spec/views/people/new.html.erb_spec.rb
spec/views/people/show.html.erb_spec.rb
spec/helpers/people_helper_spec.rb
spec/routing/people_routing_spec.rb
spec/requests/people_spec.rb

Now bring the database up to speed with the migration for the new model:

rake db:migrate

Now let's run rake - finally! The result:

...............**............

Pending:
  PeopleHelper add some examples to (or delete) /Users/peter/dev/rails/myapp/spec/helpers/people_helper_spec.rb
    # Not Yet Implemented
    # ./spec/helpers/people_helper_spec.rb:14
  Person add some examples to (or delete) /Users/peter/dev/rails/myapp/spec/models/person_spec.rb
    # Not Yet Implemented
    # ./spec/models/person_spec.rb:4

Finished in 0.31043 seconds
29 examples, 0 failures, 2 pending

Rock and roll. We're up and running. Sort of. Let's put in some "real" specs to be sure things are working nicely.

Change spec/models/person_spec.rb to the following rather contrived pair of specs:

require 'spec_helper'

describe Person do
  it "can be instantiated" do
    Person.new.should be_an_instance_of(Person)
  end

  it "can be saved successfully" do
    Person.create.should be_persisted
  end
end

Not the most useful things to spec out, admittedly, but you get a little database action and get rid of a pending spec we had cluttering things up. We haven't got anything else we can seriously test yet anyway.

Now let's run rake spec:models to focus our efforts on what we've just done:

..

Finished in 0.09378 seconds
2 examples, 0 failures

How to have specs run automatically with Watchr

Let's assume we've progressed with developing our app and we're working on models and controllers, testing along the way. Rather than running rake or bundle exec rspec all of the time, wouldn't it be great to have the relevant spec run automatically when we either edit the spec or a model/controller that has a spec? Well, with watchr, we can. (Note: Some people prefer autotest. I find watchr more flexible and useful for other things beyond just running specs.)

But if you really want to use autotest, Mike Bethany explains how to set it up in a similar scenario in a post of his own, along with autotest-growl for OS X notifications.

Add watchr to your Gemfile's testing and production gem section:

group :development, :test do
  gem 'rspec-rails'
  gem 'watchr'
end

Then run bundle to install it.

Next, create a file called .watchr in your app's root folder and populate it with this code:

def run_spec(file)
  unless File.exist?(file)
    puts "#{file} does not exist"
    return
  end

  puts "Running #{file}"
  system "bundle exec rspec #{file}"
  puts
end

watch("spec/.*/*_spec\.rb") do |match|
  run_spec match[0]
end

watch("app/(.*/.*)\.rb") do |match|
  run_spec %{spec/#{match[1]}_spec.rb}
end

This 'watchr script' directs a running watchr process to do a few things:

  • If any file ending in _spec.rb under the spec/ directory changes, run the run_spec method with its filename.
  • If any .rb file under the app/ directory changes, call the run_spec method with an equivalently named _spec.rb file under spec.
  • run_file accepts a filename for a spec file, checks it exists, and tells the system to run it (using system)

If you now run watchr .watchr to use the .watchr script, not much will happen. But if you make any change (or even just re-save) to, say, spec/models/person_spec.rb, that spec will run automatically. Make a change to app/models/person.rb and it's the same deal. To stop watchr, CTRL+C saves the day.

Watchr can be used for a lot more than this but this is just for starters ;-)

Optionally, you might also like to create lib/tasks/watchr.rake and include the following code so you can just remember to run rake watchr instead (it's nice to have anything you run within a project contained in one place):

desc "Run watchr"
task :watchr do
  sh %{bundle exec watchr .watchr}
end

How to get faster spec runs with Spork

We've got Rails 3 running with RSpec 2 and watchr's giving us some automatically-running-spec love. But do you notice how slow it is? Specs run quickly once they're loaded but there are several seconds of waiting beforehand.

If you run time rake spec:models with Ruby 1.9.2, you'll probably see a wallclock time of over 5 seconds (5.204s on my machine and I'm SSDed up) - holy splingledoops! If not, you're lucky, but it's a commonly reported problem with some improvements expected in Ruby 1.9.3. We can't wait that long though..

Enter Spork. Spork is a tool that loads the Rails environment and then forks each time you want to run some specs (or tests, it can be set up to run with Test::Unit too). In this way, the whole Rails initialization process is skipped, shaving valuable seconds off of your spec runs.

Edit your Gemfile again and include Spork:

gem 'spork', '~> 0.9.0.rc'

Run bundle to install Spork.

Next, Spork needs to make some changes to your spec/spec_helper.rb file. Because it only initializes the Rails environment once and then forks it, you might have initialization features that you need to run on each test run. Spork will let you do this but it needs to make those changes first. Run:

spork --bootstrap

The result:

Using RSpec
Bootstrapping /Users/peter/dev/rails/myapp/spec/spec_helper.rb.
Done. Edit /Users/peter/dev/rails/myapp/spec/spec_helper.rb now with your favorite text editor and follow the instructions.

Bring up spec/spec_helper.rb. All spork --bootstrap has done is add some extra code to the top of the file. Read the comments there to get a better feel for what to do and what Spork requires and keep them in mind as we progress (in case you want to do something differently).

Get rid of require 'rubygems' from the first line - we're using Bundler so it's not necessary.

Next, cut and paste all of the 'old' contents of spec_helper.rb into the Spork.prefork block. Since we're running an empty(ish) project, there's nothing special we've added that we need to run on each run using the Spork.each_run block. We can leave that empty.

You'll end up with a spec_helper.rb file that looks like this:

require 'spork'

Spork.prefork do
  # Loading more in this block will cause your tests to run faster. However, 
  # if you change any configuration or code from libraries loaded here, you'll
  # need to restart spork for it take effect.
  # This file is copied to spec/ when you run 'rails generate rspec:install'
  ENV["RAILS_ENV"] ||= 'test'
  require File.expand_path("../../config/environment", __FILE__)
  require 'rspec/rails'

  # Requires supporting ruby files with custom matchers and macros, etc,
  # in spec/support/ and its subdirectories.
  Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}

  RSpec.configure do |config|
    # == Mock Framework
    #
    # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
    #
    # config.mock_with :mocha
    # config.mock_with :flexmock
    # config.mock_with :rr
    config.mock_with :rspec

    # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
    config.fixture_path = "#{::Rails.root}/spec/fixtures"

    # If you're not using ActiveRecord, or you'd prefer not to run each of your
    # examples within a transaction, remove the following line or assign false
    # instead of true.
    config.use_transactional_fixtures = true
  end
end

Spork.each_run do
  # This code will be run each time you run your specs.
end

Head back to your shell and the root of your project and run spork:

Using RSpec
Loading Spork.prefork block...
Spork is ready and listening on 8989!

Now we're cooking with gas. Open another shell, head to the root of your project, and run watchr .watchr too. Then head to spec/models/person_spec.rb in your text editor and re-save it (or even make a change if you want). Your specs run but.. they're no faster! What's wrong?

It turns out we need to make another change so that RSpec knows we're running Spork. Edit the .rspec file (mentioned earlier) and add --drb to the line (so it probably reads --colour --drb). Now, edit the spec again, save, and.. fast!

You should note that if you use rake at this point to run your entire suite, it'll still not be particularly fast because rake itself is initializing Rails in order to do its job. But if you want to run your entire suite quickly, just run:

rspec spec

With our dummy app and on my machine, this runs in a wallclock time of 0.759s - a serious improvement over 5.2 seconds.

We have Rails 3, RSpec 2, watchr, spork, and SUPER-DUPER FAST SPECS all running on Ruby 1.9.2. Score!

A minor snafu will remain, though. If you update app/models/person.rb, the change won't take effect in your tests since Spork has the old Person still in memory. One way around this is to edit config/environments/test.rb and change:

config.cache_classes = true

To:

config.cache_classes = false

Now your app's classes are reloaded when necessary.