Ruby Weekly is a weekly newsletter covering the latest Ruby and Rails news.

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

By Peter Cooper / February 20, 2011

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.

Comments

  1. Dan Kubb says:

    While watchr is nice, the last few weeks I've been testing out guard, with guard-rspec and guard-spork (among others) and I've been really liking it. There's a ton of plugins, and like watchr you can do much more than run tests automatically, you can even execute arbitrary commands when a file changes.

  2. Walter kharma@gmail.com says:

    Thank for writing this up, it is extends the normal setup of rspec, autotest (my preference) and spork. Nice touch with the graphics!

  3. Jimmy Cuadra says:

    I like the way autotest runs all the specs when you first start it, and again when you hit control-C, in addition to watching files that change. Is there a way to make watchr behave similarly?

  4. Peter Cooper says:

    I use guard to run spork (and restart it under certain conditions) as well as Watchr. I must admit, though, I hadn't looked into the differences between the two. I'd just assumed guard was better at managing processes, perhaps. I'll have to look into it again.

  5. Peter Cooper says:

    I knew I'd seen this before, Jimmy, and just dug it out again. The answer is.. yes :-)

    See https://github.com/intridea/oauth2/blob/master/specs.watchr

    Essentially it adds a Signal.trap 'INT' do .. end block to .watchr to perform those tasks on a single Ctrl+C (and quit for real with a double CTRL+C). I rarely run the entire suite so I haven't found need for it, but it's possible.

  6. Joe Ellis says:

    Thanks for this, I was wondering just yesterday how I could speed up rspec. Perfect timing, thanks!

  7. Arun Agrawal says:

    Very nice. Using Spork before.

    I will use watchr now.

  8. Vladimir Zhukov says:

    I use Spork with Rubymine. Rubymine has autospec support. The one annoying thing was that I had experienced the problem when I change my models, there were no effect in tests. I was wonder why even after setting `config.cache_classes = false` in test environment a change won't take effect in my tests.

    So with additional discovery I found that the reason was in my Mongoid and in Devise. So the solutions came from this wiki:

    # Mongoid likes to preload all of your models in rails, making Spork a near worthless experience.
      # It can be defeated with this code:
      require "rails/mongoid"
      Spork.trap_class_method(Rails::Mongoid, :load_models)
    
      # Devise likes to load the User model. We want to avoid this. Delay route loading:
      require "rails/application"
      Spork.trap_method(Rails::Application, :reload_routes!)
    

    Thanks for article! Now my tests are super-duper fast!

  9. Peter Cooper says:

    @Vladimir: I'd been bumping into that issue with Devise! Thanks for the solution :-)

  10. Peter Cooper says:

    Eugh, WordPress is borking your comment. I'll need to look into this..

  11. Michael Hartl says:

    The config.cache_classes = false line doesn't seem to be necessary on my system. Changing, e.g., the User model in the Rails Tutorial sample application seems to change the model used by Spork just fine. I can run rspec spec/models/, change the model to break the test, and then run rspec again to watch the test fail. It works with Autotest, too. If you want to reproduce my setup, you can clone the sample app repo from GitHub.

  12. Peter Cooper says:

    Interesting - I'll have to give it a go. I had no joy with updates of any models without that change. Perhaps I'm just being dense though.. wouldn't be the first time ;-)

  13. Graeme Mathieson says:

    So, is .foo the new Foofile? ;-)

  14. Aslam Najeebdeen says:

    Thanks you very much for the write up.

    I was skeptic why is the spork doesn't load my models. Now everything work perfect!

    Cheers!

  15. Steve Lorek says:

    Thanks for the quick walkthrough. If anyone plans to use Shoulda in their tests, please bear this in mind: StackOverflow: Rspec, shoulda and spork does not work together

  16. Mike Bethany says:

    This look suspiciously like an article I posted 10 days earlier. http://mikbe.tk/2011/02/10/blazingly-fast-tests/.

    It's different enough, and better written, since he used Spork 0.9.0rc and Watchr but the structure and some of the phrases are eerily similar.

  17. Dennis says:

    Is it Windows doing me in again? - some day I hope to get that Mac ...

    I would love to get spork working - "the error message you must call Spork.using_spork!" makes little sense to me

    C:\Rails\rcia>spork
    Using RSpec
    -- Rinda Ring Server listening for connections...

    -- Starting to fill pool...
    Wait until at least one slave is provided before running tests...
    ** CTRL+BREAK to stop Spork and kill all ruby slave processes **
    you must call Spork.using_spork! before starting the server (RuntimeError)
    C:/Ruby192/lib/ruby/gems/1.9.1/gems/spork-0.9.0.rc3/lib/spork/server.rb:25:in `listen'
    C:/Ruby192/lib/ruby/gems/1.9.1/gems/spork-0.9.0.rc3/lib/spork/server.rb:20:in `run'
    C:/Ruby192/lib/ruby/gems/1.9.1/gems/spork-0.9.0.rc3/lib/spork/runner.rb:75:in `run'
    C:/Ruby192/lib/ruby/gems/1.9.1/gems/spork-0.9.0.rc3/lib/spork/runner.rb:10:in `run'
    C:/Ruby192/lib/ruby/gems/1.9.1/gems/spork-0.9.0.rc3/bin/spork:10:in `'
    C:/Ruby192/lib/ruby/gems/1.9.1/bin/spork:19:in `load'
    C:/Ruby192/lib/ruby/gems/1.9.1/bin/spork:19:in `'

    C:\Rails\rcia>:29:in `require':29:in `require': : no such file to load -- magazine_slaveno such file to load -- magazine_slave ( (LoadErroLoadErrorr)
    )
    from :29:in `require' from :29:in `require'

    from magazine_slave_provider.rb:5:in `' from magazine_slave_provider.rb:5:in `'

  18. Peter Cooper says:

    @Mike: Just to clear the air, I hadn't seen your post (or even your blog, till now) and, unusually, didn't refer to or check against any other blog posts while writing this one. I had to do this process when I started a project for a new client in mid January where I referred to several blog posts that covered different parts of this process but then I just redid it from memory for this. So I didn't rip you off - sorry(?) :-) I come from a journalistic background and I have too much to lose to lift people's work (though like most writers, I do "steal" the odd idea here and there!)

    You seem to do some different things in yours like the method deletion/loading part - I don't know if that's needed in Spork 0.9 though I haven't seemed to need it so far. You also have the file reloading and dependency clearing which hasn't seemed necessary for my project either.

    I'm intrigued which phrases are eerily similar though. Our writing styles are quite different and nothing stuck out to me..

    Also, where is autotest mentioned in yours? Or does it just work automatically? I was going to link to your post somehow if it takes a different angle with the toolset because some people might prefer to use autotest, but I didn't see where it went into this.

  19. Greg Lappen says:

    Nice article, tempted to try spork out as rspec/autotest is quite slow. It would be nice if there were an easy way to have it run under spork on my machine, but still run the traditional way on someone else's machine or on a CI server...guess it would be a matter of putting some conditional logic in spec_helper to do this.

  20. Mike Bethany says:

    Peter Cooper, I agree. I re-read your article and it's very different. Some of the phrases struck me as similar at the time but in retrospect I'm wrong. It was just synchronicity and I apologize for my hasty mischaracterization.

    I use autotest and originally had it in my post but removed that portion from my article when I was trying to edit it down to the most important parts. That bit was still in my head though when I read your article.

    Your article is vastly superior in every way. It makes me realize I have to step up my game and do a much better job. I again apologize for seeing smoke where there was no fire. Please feel free to delete my previous post with my humblest apologies.

  21. Peter Cooper says:

    Don't be so down on yourself - no offence taken! :-)

    Perhaps you would be interested in reviving your autotest work or writing a new article about how you'd use autotest with the RSpec & spork mechanism? There are clearly people who prefer it and I would link to it from here so that people have the choice. If you do anything like this, just post a link here and I'll sort it out.

  22. Mike Bethany says:

    Done, I've added the Autotest info and cleaned up my article a bit.

    http://mikbe.tk/2011/02/10/blazingly-fast-tests/

  23. Jacob says:

    This article has made my life better.

  24. Peter Cooper says:

    Thanks Mike - I've added a link in the relevant section.

  25. George Anderson says:

    "run_file accepts a filename for a spec file..." => "run_spec accepts a filename for a spec file..."

  26. Kresimir Bojcic says:

    Thanks, exactly what I was looking for... Now only to learn RSpec :)

  27. Pingback: Athens Ruby Meetup #5 | Niobium Labs

  28. Torsten says:

    Hello

    for me the config.cache_classes = false in test.rb doesnt work under Windows 7 and Ruby 1.9.2. Is there anybody with a Windows machine and reloading model classes?

  29. Suraj Kurapati says:

    There is also test-loop, my answer to Autotest, Spork, and friends. Simply run `test-loop` in any Ruby project and you've got yourself a continuous testing daemon. For Rails integration and OSD notifications, simply require 'test/loop/rails' and 'test/loop/notify' respectively in a `.test-loop` file in the current directory or in your test/spec helper file. Cheers.

  30. Corey says:

    Thanks for this write up!! Everything works perfect!

  31. ylluminate says:

    I don't know, after a lot of playing and fiddling, it really seems that guard-spork + guard-rspec may be better than spork + autotest. I don't feel that I have a solid handle on it all yet due to being so new to ruby, so if anyone could put together a very solid writeup I think we'd all benefit greatly from it.

    The guard series of gems seem to work really well together. Was interested to see guard-pow from 37signals.

Other Posts to Enjoy

Twitter Mentions