Speedy Test Iterations for Rails 3 with Spork and Guard

Posted on by in Development, Process

Overview

TDD is fun, right? Rails enthusiasts and agile evangelists alike agree. Waiting for your tests to run, however, makes for a frustrating experience. When the time between test iterations is magnified by bloated tests it can be hard to maintain a purist’s test-driven approach.

After looking into autotest, parallel testing, and in-memory databases, I have found the simplest solution to this problem in Spork and Guard, two great gems that help speed up your tests.

With proper setup and understanding of how these two tools work, you can start down the path to a more responsive test environment and reignite your love for TDD.

Assumptions

This article assumes you are on a Mac Platform, running Rails 3 on Ruby 1.9.2. The instructions should be the same or similar for other enviroments, but I have not tested that assumption. Please, leave a comment if you have trouble with your environment and I will be glad to update the article to reflect the changes.

At the time of writing, the gems referenced are versioned as follows:

  • rails 3.0.1
  • rspec-rails 2.0.1
  • rspec 2.0.0
  • spork 0.8.4
  • guard 0.2.2
  • guard-spork 0.1.3
  • rb-fsevent 0.3.9

Install Spork

Full instructions for installing Spork can be found on RailsTutorial.org

Install Guard

Guard is a modular filesystem event monitor utility written in Ruby. We will utilize a plugin for Guard that allows us to monitor changes to Rails files and restart Spork when necessary:

  1. In your Gemfile:
    group :test do
      gem 'spork'
      gem 'rb-fsevent'
      gem 'guard-spork'
      # ...
    end
    
  2. Install via bundle:

    $ bundle install

Configure Rspec to use Spork for testing

In order to get rspec to utilize the prefork functionality of spork, we need to tell it to use a drb server. In the rails root directory, create or edit the .rspec file and add the following line:

--drb

Start Spork for RSpec Manually

You can start spork for rspec manually by running the following command in your rails root path:

$ bundle exec spork

You should see the following in your terminal if it starts successfully:

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

Start Spork via Guard

The preferred way to start spork, however, is via Guard. This will help make sure that the spork server is restarted when we change files that it relies on.

The first thing we need to do is to tell Guard about spork:

$ guard init spork

This will create a file, named ‘Guardfile’ with the following contents:

guard 'spork' do
  watch('^config/application.rb$')
  watch('^config/environment.rb$')
  watch('^config/environments/.*.rb$')
  watch('^config/initializers/.*.rb$')
  watch('^spec/spec_helper.rb')
end

Basically, the ‘watch’ directives specify paths to observe for changes. If any changes occur on those files, Spork will be restarted.

Now let’s go ahead and start spork:

$ guard start

You should see the following in your terminal if it starts successfully:

Guard is now watching at '/dev/rails_app'
Starting Spork for RSpec
Spork for RSpec successfully started

Guidelines for Testing with Spork

Spork speeds up TDD significantly, but I have seen an occasional false positive due to human error. I have found the following approach to be a surefire way to balance the test speed with ensuring test stability:

  1. Start by running a full rake
  2. Before you start coding start Spork via Guard
  3. Ensure Spork starts successfully (guard will show an error if it cannot start spork)
  4. Start writing your tests
  5. Each time you run your test, specify the line number of the innermost describe or context which you are working in:

    $ rspec /path/to/my/file:22

  6. When you complete a test for a single task for your feature, run rspec on the whole file:

    $ rspec /path/to/file

  7. When your entire spec is passing, kill spork and re-run the spec without Spork
  8. Commit your changes, rinse and repeat

Troubleshooting Spork

If you find that you are working on a feature with Spork running and Rspec does not pick up your changes on each run, stop spork and try running your spec without it. If that fixes it, take a look at the spec/spec_helper.rb file. There are two blocks here, one labeled Spork.prefork do and one labeled Spork.each_run do. You can use the each_run block to load requirements for each test pass instead of prefork, which loads once before the entire suite is run.

Use Spork workarounds only when running Spork

To get Spork to work with rails, there are a couple workarounds implemented in the installation step. Our CI environment does not run Spork, so these workarounds are unnecessary. Let’s tell Rails to use these workarounds only if Spork is Running:

  1. In spec/spec_helper.rb:
      if Spork.using_spork? 
        ActiveSupport::Dependencies.clear
        ActiveRecord::Base.instantiate_observers
      end
    
  2. In your config/application.rb:
        ### Part of a Spork hack. See http://bit.ly/arY19y
        if Rails.env.test? && defined?(Spork) && Spork.using_spork?
          initializer :after => :initialize_dependency_mechanism do
            # Work around initializer in railties/lib/rails/application/bootstrap.rb
            ActiveSupport::Dependencies.mechanism = :load
          end
        end
    

Fix ActiveRecord Observers not loading

The Spork “hacks” (really just configurations for the prefork server) clear all dependencies during the Spork.prefork block. This causes an issue with ActiveRecord Observers. Add this line to your prefork block to fix:

Spork.prefork do
  #... the rest of your prefork here
  ActiveSupport::Dependencies.clear
  ActiveRecord::Base.instantiate_observers
end

Tips for your Spork each_run Block

I’ve discovered a couple useful snippets to add to your Spork each_run block. Here we go:

  1. Reload FactoryGirl’s Factories:
    # in spec/spec_helper.rb
    Spork.each_run do
      Factory.factories.clear
      Dir[Rails.root.join("spec/factories/**/*.rb")].each{|f| load f}
    end
    
  2. Reload Routes:
    # in spec/spec_helper.rb
    Spork.each_run do
      YourApplicationName::Application.reload_routes!
    end
    
  3. Use a .gitignore’d file to allow developers to dynamically modify the each_run block:
    # in spec/spec_helper.rb
    Spork.each_run do
      load "Sporkfile.rb" if File.exists?("Sporkfile.rb") 
    end
    

    Now you can manipulate the spec environment as you’re working and discard changes when you’re done.

    # in Sporkfile
    # Reload some tricky controller that's acting up with Spork
    load "app/controllers/my_tricky_controller.rb"
    

Statistics and Summary

I have found Spork to significantly, if not drastically decrease iterative test time. Statistics I’ve gathered follow:

Spec Type # Tests Time: Spork + Rspec Time: Rspec Savings
Model 63 3s 16s 13s
Controller 47 6s 19s 13s
All Models 384 total 5s 26s 21s
All Controllers 321 total 25s 44s 19s