One 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.
Have you looked at spork? It maintains a server in the background for the tests to run on so when you press go on the tests you dont have to wait for rails to fire up.
https://github.com/timcharper/spork
Ed
@ed – yes, we used spork (and guard) for a while. They’re great tools, but they solve the wrong problem. See Why I don’t use Spork for my rationale here.
Gold. I’ll be using this in one of my projects. Thanks.
This issue of Devise::TestHelpers’ sign_in requiring database interaction was troubling me so I’ve been searching around. That’s how I found this article – which is good. But I was also pleasantly surprised to find a Devise-endorsed article on the subject: https://github.com/plataformatec/devise/wiki/How-To:-Stub-authentication-in-controller-specs The implementation is similar to yours but not exactly the same.
Yes, and Devise itself has moved on somewhat too.
In what way?