在请求规范中对身份验证进行存根化

89

在编写请求规范时,如何设置会话和/或存根控制器方法?我正在尝试在我的集成测试中对身份验证进行存根 - rspec / requests

这是一个测试的示例

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)>'
5个回答

106
一个请求规范是对 ActionDispatch::IntegrationTest 的轻量级包装,它不像控制器规范(其包装 ActionController::TestCase)。即使有一个可用的会话方法,我认为它没有得到支持(即它可能存在于其他实用程序的包含模块中,也包括该方法)。
我建议通过发布到用于验证用户的任何操作来登录。如果您将所有User工厂的密码设置为“password”(例如),则可以执行以下操作:

1
谢谢David。这个方法非常好用,但是好像发送了太多的请求? - Jonas Bylov
20
如果我觉得这太过了,我就不会推荐了 :) - David Chelimsky
6
这也是最可靠的方式。 ActionDispatch::IntegrationTest 的设计目的是模拟一个或多个用户通过浏览器进行交互,而无需使用真实的浏览器。在单个示例中可能会有多个用户(即会话)和多个控制器,并且会话/控制器对象是在最后一个请求中使用的。在请求之前,您无法访问它们。 - David Chelimsky
17
我需要使用page.driver.post和Capybara一起使用。 - Ian Yang
@IanYang 根据 Jonas Nicklas 在 Capybara and testing APIs 中的说法,page.driver.post 可能是一种反模式。 - Epigene
我认为这是一个不错的解决方案:https://makandracards.com/makandra/37161-rspec-devise-how-to-sign-in-users-in-request-specs - Ismail Akbudak

64

Devise 用户请注意...

顺便说一下,如果您正在使用 Devise,那么 @David Chelimsky 的答案可能需要稍作修改。根据 这篇 StackOverflow 帖子 中的建议,我正在进行集成/请求测试:

# 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

2
当我在RSpec模型规范中使用“login user1”时,我会得到一个未定义的本地变量或方法“user_session_path”的错误信息,其代码如下:#<RSpec::Core: - jpw
1
иү™еЃ‡и®ңдҢ е·Із»ЏењЁconfig/routes.rb文件中添加дғ†devise_forпәљusers。如жһњдҢ жЊ‡е®љдғ†дёҚеђЊзљ„е†…е®№пәЊдҢ йњЂи¦ЃйЂ‚еҢ“и°ѓж•өдҢ зљ„д»Әз ЃгЂ‚ - fearless_fool
这对我有用,但我必须稍微修改一下。由于我的应用程序使用用户名作为登录名而不是电子邮件,因此我将'user[email]' => user.email更改为'user[username]' => user.username - webdevguy
你应该使用它的 sign_in 测试助手。你可能会觉得传入一个用户很有用,在控制器中获取对 current_user 响应的控制权。以下是使用工厂和已确认特性的示例:let(:user) { create(:user, :confirmed) } before { sign_in(user) }。别忘了 include Devise::Test::IntegrationHelpers ;) - Thiago Petrone

3

顺便说一下,在将我的Test::Unit测试移植到RSpec时,我希望能够在我的请求规范中使用多个(devise)会话进行登录。经过一些挖掘,我让它对我起作用了。使用Rails 3.2.13和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

并且...

# 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

编辑: 修正了打字错误


-1

你也可以很容易地对会话进行存根处理。

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

所有的 Ruby 特殊运算符实际上都是方法。调用 1+1 和调用 1.+(1) 是一样的,这意味着 + 只是一个方法。同样地,session[:user_id] 等同于在 session 上调用 [] 方法,即 session.[](:user_id)

这似乎是一个合理的解决方案。 - superluminary
2
这在请求规范中不起作用,只能在控制器规范中使用。 - Machisuji


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接