Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stub authentication in request specs

I'm looking for the way to do this but in request specs. I need to log in and log out a double or instance_double to Devise instead of an actual ActiveModel/ActiveRecord.

By using the code in the wiki page:

module RequestSpecHelpers
    def sign_in(user = double('user'))
      if user.nil?
        allow(request.env['warden']).to receive(:authenticate!).and_throw(:warden, {:scope => :user})
        allow(controller).to receive(:current_user).and_return(nil)
      else
        allow(request.env['warden']).to receive(:authenticate!).and_return(user)
        allow(controller).to receive(:current_user).and_return(user)
      end
    end
  end

I get this error: undefined method 'env' for nil:NilClass

I saw this question and this wiki, but if I want to use doubles of the user those two don't work. I was using the last one, works fine with a real user but with a double it doesn't log it in.

The tests:

RSpec.describe 'new shipment', type: :request do
  describe 'authenticated as user' do
    before do
      @user = double(:user, id: 1, email: '[email protected]', password: 'password',
                      id_card: '4163649-1', first_name: 'Jane', last_name: 'Doe')

      sign_in @user
    end
  end
end

If I include:

RSpec.configure do |config|
  config.include Devise::TestHelpers, :type => :requests
end

I get this error:

Failure/Error: @request.env['action_controller.instance'] = @controller

     NoMethodError:
       undefined method `env' for nil:NilClass
     # /root/.rbenv/versions/2.4.2/lib/ruby/gems/2.4.0/gems/devise-4.3.0/lib/devise/test/controller_helpers.rb:40:in `setup_controller_for_warden'

Problem with Frederick Cheung answer

If I do that the login_asmethod doesn't fail but it doesn't really log the user in. So when I try to access a path that has a before_action :authenticate_user! callback it fails.

Here is my code based on his answer:

require 'rails_helper'

RSpec.describe 'new shipment', type: :request do
  describe 'authenticated as user' do
    include Warden::Test::Helpers

    before(:each) do
      Warden.test_mode!
      #stub more methods as needed by the pages you are testing
      user = instance_double(User, to_key: 1, authenticatable_salt: 'example')
      login_as(user, scope: 'user')
    end

    it 'returns 200 Ok' do
      get new_shipment_path
      expect(response).to have_http_status(:ok)
    end
  end
end

And this is the response when running rspec:

 1) new shipment authenticated as user returns 200 Ok
     Failure/Error: expect(response).to have_http_status(:ok)
       expected the response to have status code :ok (200) but it was :found (302)
     # ./spec/requests/shipments_requests_spec.rb:41:in `block (3 levels) in <top (required)>'

As you can see instead of allowing me to access the path it redirects me, this is the usual behavior when the user is not allowed to access the path.

It I change the instance_double for a real User saved in the database this approach works correctly:

# only changed this line in the before hook
user = User.create(email: '[email protected]', password: 'password',id_card: '4163649-1', first_name: 'Jane', last_name: 'Doe')

Result:

Finished in 3.23 seconds (files took 33.47 seconds to load)
1 example, 0 failures
like image 536
moondaisy Avatar asked Nov 12 '17 00:11

moondaisy


1 Answers

It sounds like you're using Devise 3.x ( since Devise::TestHelpers was renamed in devise 4), Devise::TestHelpers is only designed to work with controller specs.

If you can upgrade to devise 4, it has separate helpers for request specs and controller tests. This is just a very thin wrapper around what warden provides, which hides all the messing around with env.

There are some extra complications when using a double - you need to stub out various methods devise calls that you might not realise.

The following worked for me

describe 'example' do
  include Warden::Test::Helpers

  before(:each) do
    Warden.test_mode!
    #stub more methods as needed by the pages you are testing
    user = instance_double(User, to_key: 1, authenticatable_salt: 'example')
    login_as(user, scope: 'user')
  end
end
like image 128
Frederick Cheung Avatar answered Nov 11 '22 01:11

Frederick Cheung