How To: Test with Capybara - heartcombo/devise GitHub Wiki

Acceptance tests for your application may require that a test user be logged in. To do this in your application you can either sign in the user using capybara by visiting the sign in url and entering valid credentials, or you can stub the logged in user.

Signing in via Capybara is perhaps the most 'correct' way since we want our tests to be as close to real world conditions as possible, however if you have more than a few tests, these few extra actions for each logged in test can be quite time consuming. The alternative is to use warden's built in stubbing actions to make your application think that a user is signed in but without all of the overhead of actually signing them in.

To do this, you'll need to include warden test helpers and turn on test mode. Include the following at the top of your file, or in a file that you are including. If you are using rspec-rails you can place these lines in a file of the spec/support directory and follow the instructions at the top of the file, spec/rails_helper.rb:

include Warden::Test::Helpers

You can also write those lines as:

RSpec.configure do |config|
  config.include Warden::Test::Helpers
end

Then, within your test, you can make a call to the warden helper login_as with a user resource and specifying the :scope => :user to 'log in' a test user.

user = User.create!(:email => '[email protected]', :password => 'f4k3p455w0rd')
login_as(user, :scope => :user)

If you are using a gem like FactoryBot you can make this process simpler.

user = FactoryBot.create(:user)
login_as(user, :scope => :user)

You will then need to create a corresponding user factory in your factories file (e.g. spec/factories.rb, test/factories.rb). This will look something like this:

FactoryBot.define do
  factory :user do
    email { '[email protected]' }
    password { 'f4k3p455w0rd' }
    # using dynamic attributes over static attributes in FactoryBot

    # if needed
    # is_active true
  end
end

If you have added any fields to your User model, you will need to add these here. For more details see the FactoryBot docs.

If you are using devise's confirmable module, you will need to ensure that the newly created user also has the 'confirmed_at' field filled in:

user = User.create!(:email => '[email protected]', :password => 'f4k3p455w0rd')
user.confirmed_at = Time.now
user.save

To make sure this works correctly you will need to reset warden after each test. You can do this by calling

Warden.test_reset! 

To ensure the reset occurs, you can place the command inside an after :example hook, as follows:

RSpec.configure do |config|
  config.after :each do
    Warden.test_reset!
  end
end

If for some reason you need to log out a logged in test user, you can use Warden's logout helper.

user = User.create!(:email => '[email protected]', :password => 'f4k3p455w0rd')
login_as(user, :scope => :user, :run_callbacks => false)
logout(:user)

According to this StackOverflow answer this will not work with controller tests, because rack middleware, including Warden, is not invoked when running controller tests. It is good for your Capybara tests, which run through rack. See also the Warden wiki entry about testing, although the examples are for miniTest.

If you're wondering why we can't just use Devise's built in sign_in and sign_out methods, it's because these require direct access to the request object which is not available while using Capybara. To bundle the functionality of both methods together you can create a helper method.

Capybara-Webkit

If you have trouble using Warden's login_as method with the capybara-webkit driver, try setting run_callbacks to false in the login_as options struct

user = User.create!(:email => '[email protected]', :password => 'f4k3p455w0rd')
login_as(user, :scope => :user, :run_callbacks => false)

If your login works using Capybara/Rack::Test, but not in Capybara/Selenium

make sure to set transactional fixtures to false in your Rspec spec_helper.rb file

RSpec.configure do |config|
  config.use_transactional_fixtures = false
end

See answers here for details: http://stackoverflow.com/questions/6154687/rails-integration-test-with-selenium-as-webdriver-cant-sign-in

See also: http://stackoverflow.com/a/12330557/3137294

This can solve problems with transactions - in particular, if you're seeing this error:

Mysql::Error: Lock wait timeout exceeded; try restarting transaction:

try turning off transactional fixtures and using DatabaseCleaner to prune your database.

Capybara and Poltergeist

login_as may not work with Poltergeist when js is enabled on your test scenarios. To fix this, add this file to your app.

See this SO question for more explanation

Some more up to date shared DB connection details are here

Flaky logins with JavaScript drivers (login "randomly" lost)

If you use a JavaScript driver (Cuprite, or Selenium + Chrome), you may occasionally see a spec fail with the user logged out despite a login_as in the before block — you land on the sign-in page instead of the page under test, and it passes on a re-run.

login_as doesn't log in immediately; it queues a one-shot block on Warden._on_next_request, applied to the first non-asset request to reach Warden. With a JS driver, a request leaking from the previous example (an in-flight XHR the browser hadn't finished when Capybara reset the session) can reach Warden before your own visit and consume that queued login against a throw-away session, leaving your real request unauthenticated.

Note the usual Warden.test_reset! in an after hook does not address this — it clears stale queued logins, but here a stale request consumes a fresh login. See teamcapybara/capybara#702 and #1692 for the underlying race.

One robust fix is to bind the queued login to the request your example actually makes, using a marker HTTP header (a leaked request can't carry it — Chrome won't add a header to an already-dispatched request), and apply the login only to the request carrying it. This keeps one-shot semantics, so logout still works. A drop-in module and a deterministic reproduction spec are available here: https://gist.github.com/maikels-agent/646c8b29cf381156e5719d5e7e450771


Good luck and happy testing. Blog post with more details