How To Get Rails 3 and RSpec 2 Running Specs Fast (From Scratch)
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 therspec
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 appspec/spec_helper.rb
- a file that's loaded by every spec (not in any automatic way but most haverequire '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 thespec/
directory changes, run therun_spec
method with its filename. - If any
.rb
file under theapp/
directory changes, call therun_spec
method with an equivalently named_spec.rb
file underspec
. run_file
accepts a filename for a spec file, checks it exists, and tells the system to run it (usingsystem
)
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 most casinos not on gamstop tend to use 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.
February 20, 2011 at 5:51 pm
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.
February 20, 2011 at 6:13 pm
Thank for writing this up, it is extends the normal setup of rspec, autotest (my preference) and spork. Nice touch with the graphics!
February 20, 2011 at 6:28 pm
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?
February 20, 2011 at 8:56 pm
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.
February 20, 2011 at 8:59 pm
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.February 21, 2011 at 1:19 am
Thanks for this, I was wondering just yesterday how I could speed up rspec. Perfect timing, thanks!
February 21, 2011 at 7:36 am
Very nice. Using Spork before.
I will use watchr now.
February 21, 2011 at 10:47 am
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:
Thanks for article! Now my tests are super-duper fast!
February 22, 2011 at 12:05 am
@Vladimir: I'd been bumping into that issue with Devise! Thanks for the solution :-)
February 22, 2011 at 12:06 am
Eugh, WordPress is borking your comment. I'll need to look into this..
February 22, 2011 at 4:04 am
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.
February 22, 2011 at 4:40 am
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 ;-)
February 22, 2011 at 2:13 pm
So, is
.foo
the newFoofile
? ;-)February 22, 2011 at 2:24 pm
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!
February 23, 2011 at 11:52 am
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
February 23, 2011 at 5:50 pm
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.
February 24, 2011 at 2:36 am
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 `'
February 24, 2011 at 4:09 am
@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.
February 24, 2011 at 6:03 pm
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.
February 26, 2011 at 3:08 am
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.
February 26, 2011 at 3:16 am
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.
February 26, 2011 at 6:43 am
Done, I've added the Autotest info and cleaned up my article a bit.
http://mikbe.tk/2011/02/10/blazingly-fast-tests/
February 26, 2011 at 8:50 pm
This article has made my life better.
February 27, 2011 at 2:43 am
Thanks Mike - I've added a link in the relevant section.
March 2, 2011 at 6:55 pm
"run_file accepts a filename for a spec file..." => "run_spec accepts a filename for a spec file..."
March 2, 2011 at 8:06 pm
Thanks, exactly what I was looking for... Now only to learn RSpec :)
Pingback: Athens Ruby Meetup #5 | Niobium Labs
March 9, 2011 at 11:01 pm
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?
March 29, 2011 at 5:11 am
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.
April 27, 2011 at 3:41 pm
Thanks for this write up!! Everything works perfect!
May 9, 2011 at 3:20 am
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.