Speed up Capybara Tests with Devise
16 Jan 2012All good developers should write tests, and anyone with a high stake in a web app should write acceptance tests. Acceptance tests use a web driver like Capybara to test the full functionality of your web app by interacting directly with view elements the same way a user would (clicking links, filling out forms, etc.)
My only problem with acceptance tests is that they can be a bit slow, so i’m always looking for ways to speed mine up. One of my pain points is since everything is done via manual interaction with a headless website, any test requiring a logged in user (most of them) also requires that before it is run, Capybara must log the user in by visiting the sign in path and entering valid credentials. While I still think manual sign in should be tested, it doesn’t need to be tested every single time we run another test.
If you’re doing authentication with Devise to speed things up a bit we can stub out a logged in user with Warden’s built in test helpers.
It works like this when you’re running a non-acceptance test we want to use Devise’s sign_in
helper since we have direct access to the request
object (not available during capybara/acceptance tests). All other times we want to use Warden’s login_as
method.
Here is an example of as_user
and as_visitor
helpers that do just that:
include Devise::TestHelpers
# gives us the login_as(@user) method when request object is not present
include Warden::Test::Helpers
Warden.test_mode!
# Will run the given code as the user passed in
def as_user(user=nil, &block)
current_user = user || Factory.create(:user)
if request.present?
sign_in(current_user)
else
login_as(current_user, :scope => :user)
end
block.call if block.present?
return self
end
def as_visitor(user=nil, &block)
current_user = user || Factory.stub(:user)
if request.present?
sign_out(current_user)
else
logout(:user)
end
block.call if block.present?
return self
end
I then needed to call Warden.test_reset!
after each test to ensure correct functionality
RSpec.configure do |config|
config.after(:each) { Warden.test_reset! }
end
Then in my tests (shown here with capybara/rspec, I can simply log in a user like this:
let(:user) { Factory.create(:user) }
# To use the methods you can call
# methods directly on them like this:
scenario 'works while logged in' do
as_user(user).visit teach_path
# ...
# Or you can pass the code you wish
# to run in a block:
scenario 'creating a class' do
as_user(user) do
visit teach_path
click_link('Create')
current_path.should == new_course_path
fill_in 'course_title', :with => course_stub.title
fill_in 'course_teaser', :with => course_stub.teaser
fill_in 'course_experience', :with => course_stub.experience
click_button 'Submit'
# ...
It’s that simple. With a few lines of code I was able to speed up my tests and keep all expected behavior. For more details on how this works, you can go to the wiki page I created: https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara . Good luck and happy testing!