Blazingly Fast Tests Using Spork, Rails 3, and RSpec 2
Update:
This article on Ruby Inside is a much better version of the information provided here and most importantly it shows how to use the latest release candidate of Spork, 0.9.0rc, for better Rails 3.0 support. The author also uses Watchr for continuous integration testing. I’ve now added the previously missing info for Autotest but the Ruby Inside article is an all around better article; you should check it out.
The problem
Even a small Rails app can take several seconds to complete its testing but you should note that the actual specs take only fractions of a second to run. The main reason for this lackadaisical performance is the Rails environment needs to reload every time the tests are run.
So, what can we do about this? How can we make a usable, fast system for real people in a real world development environment?
One of the easiest ways to speed things up is to use a distributed Ruby solution like Spork and as it happens RSpec has built in support for DRb! Yea!
Spork will handle loading the Rails environment and will keep it loaded so you won’t be left twiddling your thumbs every time you run your tests. This can be a huge time saver when you start doing continuous integration testing where your tests are run automatically every time you change a file.
At this point you’re probably thinking, “Great so I’ll just install Spork and follow the instructions, it’s easy right?” Well, yes and no. Spork is super easy to use but it doesn’t immediately support Rails 3 out of the box; you’ll need to make a few tweaks but luckily those tweaks are fairly simple.
Enough yapping, let’s get started…
Assumes the following:
Rails 3.0.x – Our application framework.
RSpec 2.x – Our test framework.
And Spork 0.8.4 (0.9.0 will make the extra tweaks unnecessary). – Our DRb service.
Also I’m using OS X 10.6.6 so the instructions are written from that perspective. You might not have to change a thing if you’re using some other flavor of *nix but you might have to change everything, who knows.
How long does it take now?
Just to see how much time we’re saving lets find out how long it takes to run our specs now. Well use the ‘time’ command in front of the rspec command like so:
$ time rspec spec/ ..... Finished in 0.11343 seconds 5 examples, 0 failures real 0m5.304s user 0m4.384s sys 0m0.910s
If you look you’ll see that our specs only took about 0.11 seconds to run but the whole process took a whopping 5.3 seconds to complete; that’s a huge difference.
Install Spork
We’ll start off by installing the Spork gem. We need to add it to our Gemfile anyway so lets do that and then use Bundler to install the gem.
[your rails app folder]/Gemfile
source 'http://rubygems.org' gem 'rails', '3.0.4' gem 'sqlite3' group :development do gem 'rspec-rails' end group :test do gem 'rspec' gem 'capybara' gem 'spork' # <= Add this line end
Now we use Bundler to install the Spork gem.
$ bundle install
Configure Rails
With the Spork gem installed we need to tell our Rails app how to use it so lets run Spork’s bootstrap option:
$ spork --bootstrap
The first fix we’re going to add makes sure if we delete a method that change is tracked by Spork. Open up your application.rb file and add this block inside the Application class:
if Rails.env.test? initializer :after => :initialize_dependency_mechanism do ActiveSupport::Dependencies.mechanism = :load end end
With that block added your application.rb file should look something like this:
[your rails app folder]/config/application.rb
require File.expand_path('../boot', __FILE__)
require 'rails/all'
Bundler.require(:default, Rails.env) if defined?(Bundler)
module SampleApp
class Application < Rails::Application
config.encoding = "utf-8"
config.filter_parameters += [:password]
# Add this for Spork
if Rails.env.test?
initializer :after => :initialize_dependency_mechanism do
ActiveSupport::Dependencies.mechanism = :load
end
end
end
end
Configure RSpec
If you take a look at your spec/spec_helper.rb file you’ll see two blocks added by Spork. The Spork.prefork block is run once while the appropriately named Spork.each_run is run every time your tests are run. It’s important to note that the code in each block is only loaded once, when you start Spork, so any changes to these blocks will require a restart of Spork.
ActiveSupport::Dependencies.clear
You’ll want to grab everything that has something to do with Rails and drop it into the Spork.prefork block. Anything that takes a long time to load and doesn’t need to be reloaded for every test can be put in the prefork block which is pretty much everything. If you’re not sure you can start by putting it in the prefork block and if it doesn’t work move it to the each_run block later.
You’ll also need to add this line to the end of the RSpec.configuration block:
Next you want to tell Spork to reload all the application files on every run so it catches any changes. I also like to have it load the routes.rb file so I don’t have to reload Spork every time I add a new route. When you’re first designing your app and adding routes every few minutes you’ll get really tired of reloading Spork.
With the changes your Spork.each_run block will look about like this:
Spork.each_run do
load "#{Rails.root}/config/routes.rb"
Dir["#{Rails.root}/app/**/*.rb"].each { |f| load f }
end
When you’ve got all the changes made to your spec_helper.rb file it should look a bit like this:
[your rails app folder]/spec/spec_helper.rb
require 'rubygems'
require 'spork'
Spork.prefork do
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
RSpec.configure do |config|
config.mock_with :rspec
config.fixture_path = "#{::Rails.root}/spec/fixtures"
config.use_transactional_fixtures = true
# Needed for Spork
ActiveSupport::Dependencies.clear
end
end
Spork.each_run do
load "#{Rails.root}/config/routes.rb"
Dir["#{Rails.root}/app/**/*.rb"].each { |f| load f }
end
Our final configuration tells RSpec to use Spork by adding the —drb command line switch to the .rspec file.
[your rails app folder]/.rspec
--color --drb
If you’re sure you want to use Spork for all the apps you use RSpec with you can add it to ~/.rspec instead but I’d hold off on that till you’re sure.
Start Using Spork!
We’re all done with the configuration the only thing left to do is start up Spork. You’ll want to run it in its own shell so open up a new terminal window and run the spork command from the root of your Rails app:
$ spork Using RSpec Loading Spork.prefork block... Spork is ready and listening on 8989!
Run RSpec
Now every time you run RSpec for that app it will use Spork and your tests will be much faster:
$ time rspec spec/ ..... Finished in 0.12398 seconds 5 examples, 0 failures real 0m0.981s user 0m0.425s sys 0m0.124s
See how much faster it is?! In this case it’s over 500% faster! Of course that kind of savings won’t always scale directly with the number of your tests since most of our time in this instance is taken up just by loading the Rails environment.
That’s pretty much it; you should only have to restart Spork if you make some major changes to your Rails app like adding new gems. Basically anything that would require you to restart your test server will also require you to restart Spork; like if you change any of the configuration files other than the routes.rb file.
Continuous Integration Testing
The whole point of this exercise is to make testing faster so what’s better than having immediate feedback from your tests? Part of continuous integration is running your tests every time you compile your code but since Ruby isn’t a compiled language we can just run our tests when we save a change to our source code files.
For this immediate gratification fix I’m partial to the solution RSpec originally had built-in, Autotest. It’s incredibly easy to use and to install. From a terminal window simply type:
$ gem install autotest Successfully installed autotest-4.4.6 1 gem installed
I use the whole ZenTest suite but if you don’t need all of that you can just install autotest by itself:
$ gem install autotest-standalone
You can leave it at that and Autotest will poll for changes in any files but that’s not very efficient. Let’s install a system file hook for Autotest so it only has to work if there are changes made to our files.
If you’re using OS X you’ll want to install the autotest-fsevent gem:
$ gem install autotest-fsevent Building native extensions. This could take a while... ------------------------------------------------------------------------------- In order to use autotest-fsevent, install either the comprehensive ZenTest gem or the lightweight autotest-standalone gem and then add the following line to your ~/.autotest file: require 'autotest/fsevent' For more information, feedback and bug submissions, please visit: http://www.bitcetera.com/products/autotest-fsevent If you like this gem, please consider to recommend me on Working with Rails, thank you! http://workingwithrails.com/recommendation/new/person/11706-sven-schwyn ------------------------------------------------------------------------------- Successfully installed autotest-fsevent-0.2.4 1 gem installed
Do as the instructions say and add the following line to your ~/.autotest file:
require 'autotest/fsevent'
If you’re using a Linux based system you can install the libinotify library and then the autotest-inotify gem instead. I haven’t tested either of these so can’t give you any help with them.
Growl Notices
As an added bonus you can also install autotest-growl and get Growl notices when your tests run; assuming you have Growl installed of course. This is incredibly useful since you don’t have to bother with your terminal window. You can just look at the growl notices and only need to mess with your terminal window if you need to know where your test is failing.
$ gem install autotest-growl ------------------------------------------------------------------------------- In order to use autotest-growl, install either the comprehensive ZenTest gem or the lightweight autotest-standalone gem and then add the following line to your ~/.autotest file: require 'autotest/growl' Make sure the notification service installed on your computer: http://growl.info (Growl for Mac OS X) http://growlforwindows.com (Growl for Windows) http://www.galago-project.org (libnotify for Linux) If Growl notifications are not always displayed, take a look at the README for assistance. For more information, feedback and bug submissions, please visit: http://www.bitcetera.com/products/autotest-growl If you like this gem, please consider to recommend me on Working with Rails, thank you! http://workingwithrails.com/recommendation/new/person/11706-sven-schwyn ------------------------------------------------------------------------------- Successfully installed autotest-growl-0.2.9 1 gem installed
Again you’ll need to follow the instructions and add a line to your ~/.autotest file:
require 'autotest/growl'
Drink Beer
That’s it. You now have super-fast tests that are automatically run every time you update a code file.
If you have any problems please drop me a line; this info was cobbled together from numerous sources with my own additions to make it a little more useful.

