Speedy Test Iterations for Rails 3 with Spork and Guard

Posted on by in Process, Web

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

Feedback

  Comments: 30


  1. I ran into an issue when running specs from RubyMine 3.0 through spork:

    Exception encountered: #<LoadError: no such file to load -- teamcity/spec/runner/formatter/teamcity/formatter>

    RubyMine always requires this formatter when it runs a spec and I wasn’t able to figure out a way to turn it off. So I had to slightly modify the Spork.prefork block in spec_helper.rb as follows:

    Spork.prefork
    $:.unshift '/Applications/RubyMine 3.0.app/rb/testing/patch/bdd/'
    $:.unshift '/Applications/RubyMine 3.0.app/rb/testing/patch/common/'
    ...
    end

    And away we go…


  2. I really like rails and cucumber and spork seems to be an excellent addition but the amount and style of configuration that is required to get these tools working is just terrible. Working with rails is very smooth and all this bootstrapping and hacking just does not fit into the development process.

    Thanks for these instructions, I’ll try them when I get home, but I am veeery sure that a month from now, when I look at my hacks in the configuration files, I won’t be able to remember why each line exists. There should be a better and more consistent way to do this.


    • @tunylund: spot on note there. That is something that is very much frustrating me. This part of the “convenience” should not be so involved or required so much note taking and recall. Blah, we’ve really got to see some sort of solution for this. Rails is amazing, but this kind of junk has got to go away for BDD.


  3. Thanks for the article and it works great.
    But I couldn’t make ruby debugger (ruby-debug19) work with the above settings.
    I setup this up on ruby 1.9.2
    I really liked the speed of spork, but at the same time I can’t stand without ruby-debugger.
    Googled and found some discussions and patches, applied couple of those, but none worked out.
    Some does get hooked with the debugger, but at the same time it frozes all the running specs and the spork server too.


  4. Thanks for the nice article, but I can’t get to work Spork with Machinist. How can you load the blueprints? I am using Steak & Mongoid too.


  5. Not sure if it’s just my setup, but I was unable to see my model changes between each run with the code under the section Fix ActiveRecord Observers not loading in the prefork block as instructed above. I had to move it to the each_run block before changes were loaded between runs. Hope this helps someone else.


  6. Thanks for the helpful article – it was timely and also introduced me to Guard. One quick question – do you know how to use spork with Vim so that typing :Rake runs a sporked test?


  7. Thanks for the helpful article, this sped up my Rails 3 tests a lot.


  8. Why not just add guard-rspec, (maybe with fail_fast enabled), so that you never have to run your tests by hand?

  9. dennis oconnor


    I have set up guard and spork by following instructions above and running on CentOS. but when I start guard I get the following below. Also when I make a change to any of my files in the lombard directory no tests are run. any help would be great

    guard start
    Guard is now watching at ‘/usr/local/Lombard’
    Starting Spork for Test::Unit & RSpec & Cucumber
    Couldn’t find a supported test framework that begins with ‘testunit’

    Supported test frameworks:
    (*) Cucumber
    (*) RSpec

    Legend: ( ) – not detected in project (*) – detected
    Using RSpec
    Preloading Rails environment
    Using Cucumber
    /usr/local/Lombard/features/support/env.rb has not been bootstrapped. Run spork –bootstrap to do so.
    Loading Spork.prefork block…
    ERROR: Could not start Spork server for Test::Unit & RSpec & Cucumber. Make sure you can use it manually first.
    Preloading Rails environment
    Spork is ready and listening on 8990!
    Spork is ready and listening on 8989!


  10. @dennis o’connor
    I’m having the exact same issue. Everything works, but that error message is just plain annoying
    Any hints on how to clear it would be greatly appreciated


  11. @dennis, @Terry

    try these in Guardfile spork options, mine’s working after adding :test_unit => false


    guard 'spork', :wait => 30, :cucumber => false, :cucumber_env => { 'RAILS_ENV' => 'test' }, :rspec_env => { 'RAILS_ENV' => 'test' }, :test_unit => false do

  12. Jeroen van Dijk


    Thanks for this blog post. It is still very relevant!

    I wanted to put my observers in app/observers and have them autoloaded using config.autoload_paths.

    For this to work you need to put move ActiveRecord::Base.instantiate_observers from the prefork block to the each_run block like so:

    Spork.each_run do
    ActiveRecord::Base.instantiate_observers
    end


  13. To be honest I fell over at the first step: i.e. bundle install on the gems suggested. Okay I’m running this on Windows rather than Mac, and the article does say only tested on Mac. But it’s kind of off-putting. The problem is rb-fsevent saying it requires installed build tools. Frustrating.


  14. What is rb-fsevent used for?


    • I think guard might use it to detect file system events (ie: files being changed) without having to poll. Not mandatory for guard to work, which is why it’s not a dependency, but it is an optimization.


    • rb-fsevent is used for notifications


  15. With the newer FactoryGirl, you can just do FactoryGirl.reload. I forget when this was first introduced, but I’m using it in my each_run block on factory_girl 2.1.2 (as a dependency from factory_girl_rails 1.2.0)


  16. I always get this error:
    Couldn’t find a supported test framework that begins with ‘testunit’

    Even when i set :wait to 60 or whatever in the Guardfile. Does anyone have a solution?


    • I have the same issue. Did you find a solution?


      • Not at all… I hope that someone here has the ingenious solution, i really have no idea.
        But i´ll share my solution when i get it to work.


        • Hey all. I had this issue as well, and the primary reason is because the guard_spork gem autodetects which test frameworks to run against. So either remove your ‘test/’ directory, or write the guard section as:
          guard ‘spork’, :test_unit => false do
          #… watch declarations here
          end

          instead. I just nuked my ‘test/’ directory, and it started working, just trying to run rspec.

Your feedback