Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

devise and rspec-rails - How to sign-in user in Request type specs (specs tagged with type: :request)?

Environment

Rails 4.2.0
ruby-2.2.1 [ x86_64 ]
devise         3.4.1
rspec-core  3.2.2
rspec-rails   3.2.1

In my /spec/rails_helper.rb I have included Devise helpers for spec files tagged with type: :controller and type: :request

spec/rails_helper.rb

ActiveRecord::Migration.maintain_test_schema!

RSpec.configure do |config|
  # 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 = false

  config.before(:suite) do
    DatabaseCleaner.strategy = :transaction
    DatabaseCleaner.clean_with(:truncation)
  end

  config.before(:suite) do
    begin
      DatabaseCleaner.start
      FactoryGirl.lint
    ensure
      DatabaseCleaner.clean
    end
  end

  config.around(:each) do |example|
    DatabaseCleaner.cleaning do
      example.run # ==================> L-60
    end
  end

  config.include FactoryGirl::Syntax::Methods

  # RSpec Rails can automatically mix in different behaviours to your tests
  # based on their file location, for example enabling you to call `get` and
  # `post` in specs under `spec/controllers`.
  #
  # You can disable this behaviour by removing the line below, and instead
  # explicitly tag your specs with their type, e.g.:
  #
  #     RSpec.describe UsersController, :type => :controller do
  #       # ...
  #     end
  #
  # The different available types are documented in the features, such as in
  # https://relishapp.com/rspec/rspec-rails/docs
  config.infer_spec_type_from_file_location!

  config.include Devise::TestHelpers, type: :controller
  config.include Devise::TestHelpers, type: :request

end

With that config in place the type: controller specs runs fine. However when running type: request specs I am getting following error:

 Failure/Error: Unable to find matching line from backtrace
 NoMethodError:
   undefined method `env' for nil:NilClass
 # /home/.rvm/gems/ruby-2.2.1@myapp/gems/devise-3.4.1/lib/devise/test_helpers.rb:24:in `setup_controller_for_warden'
 # ./spec/rails_helper.rb:60:in `block (3 levels) in <top (required)>'
 # /home/.rvm/gems/ruby-2.2.1@simplyhomeapp/gems/database_cleaner-1.4.1/lib/database_cleaner/generic/base.rb:15:in `cleaning'
 # /home/.rvm/gems/ruby-2.2.1@simplyhomeapp/gems/database_cleaner-1.4.1/lib/database_cleaner/base.rb:92:in `cleaning'
 # /home/.rvm/gems/ruby-2.2.1@simplyhomeapp/gems/database_cleaner-1.4.1/lib/database_cleaner/configuration.rb:86:in `block (2 levels) in cleaning'
 # /home/.rvm/gems/ruby-2.2.1@simplyhomeapp/gems/database_cleaner-1.4.1/lib/database_cleaner/configuration.rb:87:in `call'
 # /home/.rvm/gems/ruby-2.2.1@simplyhomeapp/gems/database_cleaner-1.4.1/lib/database_cleaner/configuration.rb:87:in `cleaning'
 # ./spec/rails_helper.rb:59:in `block (2 levels) in <top (required)>'

https://github.com/plataformatec/devise/blob/master/lib/devise/test_helpers.rb#L24 is following

def setup_controller_for_warden #:nodoc:
  @request.env['action_controller.instance'] = @controller  # ==================> L-24
end

I am aware that @request instance is not available for :request type specs and hence the error.

Are there any helpers available we can use to sign-in a user in :request type specs when using Devise?

I found a similar issue https://github.com/plataformatec/devise/issues/1114, the reply to which suggests following:

If you're doing integration tests, make sure to sign in your user in the tradicional way, by filling the sign in form and submitting.

But I would like to by pass the actual login for specs which requires a signed-in user.

Thanks.

like image 357
Jignesh Gohel Avatar asked Apr 08 '15 08:04

Jignesh Gohel


2 Answers

With the help of a few SO posts(please refer to the References section below) I have managed to achieve the desired solution. I am posting my working code below, in case it can help others looking out for the same:

spec/rails_helper.rb

RSpec.configure do |config|
  ....
  ....
  config.include Devise::TestHelpers, type: :controller
  config.include Warden::Test::Helpers, type: :request
end

spec/shared_contexts.rb

RSpec.shared_context "api request global before and after hooks" do
  before(:each) do
    Warden.test_mode!
  end

  after(:each) do
    Warden.test_reset!
  end
end

RSpec.shared_context "api request authentication helper methods" do
  def sign_in(user)
    login_as(user, scope: :user)
  end

  def sign_out
    logout(:user)
  end
end

/spec/requests/api/logout_spec.rb

require 'rails_helper'
require 'shared_contexts'
      
RSpec.describe "Api Logout", :type => :request do
  include_context "api request authentication helper methods"
  include_context "api request global before and after hooks"

  let(:email) { '[email protected]' }
  let(:password) { 'password' }
        
  # Assumes you have FactoryGirl included in your application's test group.
  let!(:user) { create(:user, email: email, password: password) }

  context "DELETE /logout" do
    it "responds with 204 and signs out the signed-in user" do
      sign_in(user)

      # Till not figured out how to assert Warden has successfully logged in the user like we can do in a Devise controller spec by asserting subject.current_user. If anybody knows a way to do it please share.
      # expect(subject.current_user).to_not be_nil 

      delete "/logout"

      expect(response).to have_http_status(204)
    end
  end
end

I have still not figured out how to assert Warden has successfully logged in the user like we can do in a Devise controller spec by asserting expect(subject.current_user).to_not be_nil. If anybody knows a way to do it please share.

References

  • Integration test with rspec and devise sign_in env

  • https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara

  • https://github.com/hassox/warden/blob/master/lib/warden/test/helpers.rb

  • undefined method 'env' for nil:NilClass

  • https://github.com/plataformatec/devise/wiki/How-To:-Stub-authentication-in-controller-specs

    The code in the above link still relies on a request object which is only available in Controller specs. Thus not useful for type: :request specs.

  • https://github.com/plataformatec/devise/issues/3555

Thanks,

Jiggnesh

like image 88
Jignesh Gohel Avatar answered Nov 13 '22 20:11

Jignesh Gohel


While the popular answer here, also replicated on the Devise wiki, is ok, it is simplest to just:

spec/rails_helper.rb

RSpec.configure do |config|
  # ...
  config.include Devise::Test::IntegrationHelpers, type: :request
end

And just use sign_in in your request spec. This is the equivalent of declaring include Devise::Test::IntegrationHelpers in an system/feature spec or Rails system/controller test.

Reference

Devise wiki: How to sign in and out a user in request type specs - Simple approach

like image 39
ybakos Avatar answered Nov 13 '22 18:11

ybakos