Faster Rails Controller Specs
Join the DZone community and get the full member experience.
Join For FreeOne of the Rails apps I work on has this:
$ rspec spec #... Finished in 61.82 seconds 475 examples, 0 failures
61 seconds! (And on top of that I have to wait another 15 seconds
for Rails load; that’s a whole other story, and I hope to come back to
that in a future post.) A quick dig into the 61 seconds reveals this:
$ rspec spec/controllers #... Finished in 22.82 seconds 114 examples, 0 failures
It happens that almost every controller action in our app needs the
user to be logged in, so every controller spec needs that too. We’re
using Devise, so every spec has something equivalent to this:
before :each do sign_in Factory(:user) end
This implies hits on the database to create the user object, record
the IP address and last sign-in time, etc etc. We can do much better by
using a mock_model for the User, and stubbing out Warden‘s authentication completely.
First, in the interests of DRYness, I made some helper methods to do some of the jobs that factory_girl does:
module RandomHelpers def random_number() SecureRandom.random_number(10000); end def random_id() random_number; end def random_name() SecureRandom.hex(20); end end
Next, a method that creates a test double representing the User who will be logged in:
module ControllerHelpers def user_double(attrs = {}) user_attrs = { :first_name => random_name, :last_name => random_name, :authenticatable_salt => 'x' }.merge(attrs) mock_model(User, user_attrs) end end
This is the place to fill the User double with default attribute
values. Our app requires every User to have a first and last name, so
I’ve stubbed these with random strings; they can be overridden by the
caller if specific values are needed for any test. In your own app,
change this method to set any mandatory attributes your user model
needs. Note also the stubbed authenticatable_salt() method, which wasn’t required when mocking earlier versions of Devise.
I also need a method that logs a user in:
module ControllerHelpers def stub_sign_in_with(user) request.env['warden'] = double(Warden, :authenticate => user, :authenticate! => user, :authenticate? => true) sign_in(user) return user end end
This replaces Warden with a test double, and Devise is none the wiser.
I also create a method to glue these together for the simplest (and most common) case:
module ControllerHelpers def stub_sign_in(attrs = {}) stub_sign_in_with user_double(attrs) end end
(Note that this returns the User double; you’ll see why that’s useful in a moment.)
Finally, I configure the specs so they have access to these helper methods. To achieve that, spec/spec_helper.rb needs to look something like this:
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} RSpec.configure do |config| config.include RandomHelpers config.include Devise::TestHelpers, :type => :controller config.include ControllerHelpers, :type => :controller end
Putting all this together allows me to write controller specs with:
before do stub_sign_in end
or, if I need access to the user double:
let(:user) { stub_sign_in }
And the payoff?
$ rspec spec/controllers #... Finished in 7.36 seconds 114 examples, 0 failures
I saved as whole 15 seconds! There’s still some work to do in my
specs, but stubbing out Warden has made a massive difference to every
test run that involves this project’s controller microtests.
Note that the above has been tested only with the following gem versions:
- rails 3.0.3
- devise 1.3.4
- warden 1.0.4
- rspec-core 2.6.4
- rspec-mocks 2.6.0
- rspec-rails 2.6.1
- factory_girl 1.3.3
A condensed gist for the above code is here.
source: http://silkandspinach.net/2011/08/07/faster-rails-controller-specs/
Opinions expressed by DZone contributors are their own.
Comments