【问题标题】:unable to use sign_in method in acceptance test Rspec无法在验收测试 Rspec 中使用 sign_in 方法
【发布时间】:2018-04-18 08:01:36
【问题描述】:

我最近在我的 rails 项目中添加了 Devise 和 CanCanCan 用于身份验证和权限。

因此它破坏了我之前所做的大部分验收测试,例如:

resource 'Projects' do
   route '/projects?{}', 'Projects collection' do
     get 'Return all projects' do
       example_request 'list all projects' do
         expect(status).to eq 200

我知道为什么这段代码被破坏了:我没有@current_user,CanCan 用CanCan::AccessDenied 拒绝了请求。

我目前正在尝试对管理员用户进行身份验证,以便通过我的验收测试,因为我为管理员定义了 can :manage, :all

我偶然发现了许多类似我的帖子,但没有任何解决方案有效,因为我找到的所有答案都是为控制器测试而设计的,而 sign_in 方法显然对他们有用。

到目前为止我尝试了什么:

before(:each) do
   sign_in admin
end

NoMethodError:
   undefined method `sign_in' for #<RSpec::ExampleGroups::Projects::ProjectsProjectsCollection::GETReturnAllProjects:0x0000000005dc9948>

所以我尝试添加

RSpec.configure do |config|
 config.include Devise::TestHelpers


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

 NoMethodError:
   undefined method `env' for nil:NilClass

据我了解,我不能这样做,因为我没有在控制器范围内进行测试,但我正在测试资源,所以我没有@request 也没有@controller。

我做错了什么,如果没有,既然我包含了身份验证和权限,我该如何通过测试?

使用的版本:

cancancan (2.2.0)
devise (4.3.0)
rails (5.1.4)
ruby 2.5.0p0
rspec (3.7.0)

【问题讨论】:

  • 您使用的是哪个版本的 rails 和 devise?
  • @Thrasher 我用版本更新了我的问题
  • 你检查了吗? How To: Test with Capybara
  • 我检查了两次,同样的问题:我没有用 sign_in admin 设计,而是用 Warden 尝试了 login_as admin, scope: :user。我得到了NoMethodError:undefined method []= for nil:NilClass,这是一个同样的问题
  • 我也看了cancan official doc for testing,我已经测试了我的能力,现在我想测试我的资源,而不是控制器

标签: ruby-on-rails rspec devise cancan cancancan


【解决方案1】:

问题和描述的完全一样,我在验收测试中没有成功使用 Devise 助手。

对我来说,解决方法是从验收测试适应请求测试。

#spec/requests/projects_spec.rb

require 'rails_helper'

RSpec.describe Project, type: :request do
 let!(:admin) { create(:user, is_admin: true) }
 let!(:user) { create(:user) }

 context 'admin logged in' do
   before do
     log_in admin
   end
   it 'Return all projects' do
     get projects_path
     expect(status).to eq 200
     // more tests
   end
   // more tests
 end
 context 'Normal user logged in' do
   before do
     log_in user
   end
   // more tests
 end

log_in 方法来自我自己创建的助手

#spec/support/session_helpers.rb

module SessionHelpers
  def log_in(user, valid = true, strategy = :auth0)
    valid ? mock_valid_auth_hash(user) : mock_invalid_auth_hash
    Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[strategy.to_sym]
    get user_google_oauth2_omniauth_callback_path
  end
end

它只是在请求级别存根身份验证(注意get user_google_oauth2_omniauth_callback_path,这是我的应用的身份验证回调)

我的回调是这样配置的:

#app/config/routes.rb

Rails.application.routes.draw do
  devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }
  devise_scope :user do
    get 'sign_in', to: 'devise/sessions#new', as: :new_user_session
    delete 'sign_out', to: 'devise/sessions#destroy', as: :destroy_user_session
  end

#app/controllers/users/omniauth_callbacks_controller.rb

module Users
  class OmniauthCallbacksController < Devise::OmniauthCallbacksController
    include Devise::Controllers::Rememberable

    def google_oauth2
      @user = User.from_omniauth(request.env['omniauth.auth'])
      if @user
        sign_in_and_redirect @user
      else
        redirect_to root_path, notice: 'User not registered'
      end
    end
   // more code
  end
end

与其他帮助者一起(我的提供者是 Omniauth)

#spec/support/omniauth_macros.rb

module OmniauthMacros
  def mock_valid_auth_hash(user)
    # The mock_auth configuration allows you to set per-provider (or default)
    # authentication hashes to return during integration testing.
    OmniAuth.config.test_mode = true
    opts = {
      "provider": user.provider,
      "uid": user.uid,
      "info": {
        "email": user.email,
        "first_name": user.first_name,
        "last_name": user.last_name
      },
      "credentials": {
        "token": 'XKLjnkKJj7hkHKJkk',
        "expires": true,
        "id_token": 'eyJ0eXAiOiJK1VveHkwaTFBNXdTek41dXAiL.Wz8bwniRJLQ4Fqx_omnGDCX1vrhHjzw',
        "token_type": 'Bearer'
      }
    }
    OmniAuth.config.mock_auth[:default] = OmniAuth::AuthHash.new(opts)
  end

  def mock_invalid_auth_hash
    OmniAuth.config.mock_auth[:default] = :invalid_credentials
  end
end

我需要我的模块,所以我可以在我的请求测试中使用它们。

#spec/rails_helper.rb

Dir[Rails.root.join('spec', 'support', '*.rb')].each { |file| require file }

【讨论】:

    猜你喜欢
    • 2014-08-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-27
    • 1970-01-01
    • 2021-06-05
    相关资源
    最近更新 更多