请求规范中的存根身份验证

在编写请求规范时,如何设置会话和/或存根控制器方法? 我试图在集成测试-rspec/request 中消除身份验证

下面是一个测试的例子

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

帮手:

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

以及处理身份验证的 ApplicationController:

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

为什么不可能访问这些资源?

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)>'
38901 次浏览

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

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


# Rails 6
def login(user)
post user_session_path, params: {
user: {
email: user.email, password: user.password
}
}
follow_redirect!
end


# Rails 5 or older
def login(user)
post_via_redirect user_session_path, 'user[email]' => user.email, 'user[password]' => user.password
end

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

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)