Stubbing authentication in request spec

2019-01-05 08:55发布

When writing a request spec, how do you set sessions and/or stub controller methods? I'm trying to stub out authentication in my integration tests - rspec/requests

Here's an example of a test

require File.dirname(__FILE__) + '/../spec_helper'
require File.dirname(__FILE__) + '/authentication_helpers'


describe "Messages" do
  include AuthenticationHelpers

  describe "GET admin/messages" do
    before(:each) do
      @current_user = Factory :super_admin
      login(@current_user)
    end

    it "displays received messages" do
      sender = Factory :jonas
      direct_message = Message.new(:sender_id => sender.id, :subject => "Message system.", :content => "content", :receiver_ids => [@current_user.id])
      direct_message.save
      get admin_messages_path
      response.body.should include(direct_message.subject) 
    end
  end
end

The helper:

module AuthenticationHelpers
  def login(user)
    session[:user_id] = user.id # session is nil
    #controller.stub!(:current_user).and_return(user) # controller is nil
  end
end

And the ApplicationController that handles authentication:

class ApplicationController < ActionController::Base
  protect_from_forgery

  helper_method :current_user
  helper_method :logged_in?

  protected

  def current_user  
    @current_user ||= User.find(session[:user_id]) if session[:user_id]  
  end

  def logged_in?
    !current_user.nil?
  end
end

Why is it not possible to access these resources?

1) Messages GET admin/messages displays received messages
     Failure/Error: login(@current_user)
     NoMethodError:
       undefined method `session' for nil:NilClass
     # ./spec/requests/authentication_helpers.rb:3:in `login'
     # ./spec/requests/message_spec.rb:15:in `block (3 levels) in <top (required)>'

5条回答
看我几分像从前
2楼-- · 2019-01-05 09:33

A request spec is a thin wrapper around ActionDispatch::IntegrationTest, which doesn't work like controller specs (which wrap ActionController::TestCase). Even though there is a session method available, I don't think it is supported (i.e. it's probably there because a module that gets included for other utilities also includes that method).

I'd recommend logging in by posting to whatever action you use to authenticate users. If you make the password 'password' (for example) for all the User factories, then you can do something like this:

def login(user)
  post login_path, :login => user.login, :password => 'password'
end
查看更多
SAY GOODBYE
3楼-- · 2019-01-05 09:43

FWIW, in porting my Test::Unit tests to RSpec, I wanted to be able to login with multiple (devise) sessions in my request specs. It took some digging, but got this to work for me. Using Rails 3.2.13 and RSpec 2.13.0.

# file: spec/support/devise.rb
module RequestHelpers
  def login(user)
    ActionController::IntegrationTest.new(self).open_session do |sess|
      u = users(user)

      sess.post '/users/sign_in', {
        user: {
          email: u.email,
          password: 'password'
        }
      }

      sess.flash[:alert].should be_nil
      sess.flash[:notice].should == 'Signed in successfully.'
      sess.response.code.should == '302'
    end
  end
end

include RequestHelpers

And...

# spec/request/user_flows.rb
require 'spec_helper'

describe 'User flows' do
  fixtures :users

  it 'lets a user do stuff to another user' do
    karl = login :karl
    karl.get '/users'
    karl.response.code.should eq '200'

    karl.xhr :put, "/users/#{users(:bob).id}", id: users(:bob).id,
      "#{users(:bob).id}-is-funny" => 'true'

    karl.response.code.should eq '200'
    User.find(users(:bob).id).should be_funny

    bob = login :bob
    expect { bob.get '/users' }.to_not raise_exception

    bob.response.code.should eq '200'
  end
end

Edit: fixed typo

查看更多
一纸荒年 Trace。
4楼-- · 2019-01-05 09:46

Note for Devise users...

BTW, @David Chelimsky's answer may need a little tweaking if you're using Devise. What I'm doing in my integration / requests testing (thanks to this StackOverflow post):

# file: spec/requests_helper.rb
def login(user)
  post_via_redirect user_session_path, 'user[email]' => user.email, 'user[password]' => user.password
end
查看更多
beautiful°
5楼-- · 2019-01-05 09:47

You could pretty easily stub the session as well.

controller.session.stub(:[]).with(:user_id).and_return(<whatever user ID>)

All ruby special operators are indeed methods. Calling 1+1 is the same as 1.+(1), which means + is just a method. Similarly, session[:user_id] is the same as calling method [] on session, as session.[](:user_id)

查看更多
登录 后发表回答