devise 和 rspec-rails - 如何在请求类型规范(带有 type: :request 标记的规范)中登录用户?

Posted

技术标签:

【中文标题】devise 和 rspec-rails - 如何在请求类型规范(带有 type: :request 标记的规范)中登录用户?【英文标题】:devise and rspec-rails - How to sign-in user in Request type specs (specs tagged with type: :request)? 【发布时间】:2015-06-13 02:41:26 【问题描述】:

环境

Rails 4.2.0
ruby-2.2.1 [ x86_64 ]
devise         3.4.1
rspec-core  3.2.2
rspec-rails   3.2.1

在我的 /spec/rails_helper.rb 中,我为带有 type: :controllertype: :request 标记的规范文件添加了 Devise 助手

spec/rails_helper.rb

ActiveRecord::Migration.maintain_test_schema!

RSpec.configure do |config|
  # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
  config.fixture_path = "#::Rails.root/spec/fixtures"

  # If you're not using ActiveRecord, or you'd prefer not to run each of your
  # examples within a transaction, remove the following line or assign false
  # instead of true.

  config.use_transactional_fixtures = false

  config.before(:suite) do
    DatabaseCleaner.strategy = :transaction
    DatabaseCleaner.clean_with(:truncation)
  end

  config.before(:suite) do
    begin
      DatabaseCleaner.start
      FactoryGirl.lint
    ensure
      DatabaseCleaner.clean
    end
  end

  config.around(:each) do |example|
    DatabaseCleaner.cleaning do
      example.run # ==================> L-60
    end
  end

  config.include FactoryGirl::Syntax::Methods

  # RSpec Rails can automatically mix in different behaviours to your tests
  # based on their file location, for example enabling you to call `get` and
  # `post` in specs under `spec/controllers`.
  #
  # You can disable this behaviour by removing the line below, and instead
  # explicitly tag your specs with their type, e.g.:
  #
  #     RSpec.describe UsersController, :type => :controller do
  #       # ...
  #     end
  #
  # The different available types are documented in the features, such as in
  # https://relishapp.com/rspec/rspec-rails/docs
  config.infer_spec_type_from_file_location!

  config.include Devise::TestHelpers, type: :controller
  config.include Devise::TestHelpers, type: :request

end

使用该配置,type: controller 规范运行良好。但是,在运行 type: request 规格时,我收到以下错误:

 Failure/Error: Unable to find matching line from backtrace
 NoMethodError:
   undefined method `env' for nil:NilClass
 # /home/.rvm/gems/ruby-2.2.1@myapp/gems/devise-3.4.1/lib/devise/test_helpers.rb:24:in `setup_controller_for_warden'
 # ./spec/rails_helper.rb:60:in `block (3 levels) in <top (required)>'
 # /home/.rvm/gems/ruby-2.2.1@simplyhomeapp/gems/database_cleaner-1.4.1/lib/database_cleaner/generic/base.rb:15:in `cleaning'
 # /home/.rvm/gems/ruby-2.2.1@simplyhomeapp/gems/database_cleaner-1.4.1/lib/database_cleaner/base.rb:92:in `cleaning'
 # /home/.rvm/gems/ruby-2.2.1@simplyhomeapp/gems/database_cleaner-1.4.1/lib/database_cleaner/configuration.rb:86:in `block (2 levels) in cleaning'
 # /home/.rvm/gems/ruby-2.2.1@simplyhomeapp/gems/database_cleaner-1.4.1/lib/database_cleaner/configuration.rb:87:in `call'
 # /home/.rvm/gems/ruby-2.2.1@simplyhomeapp/gems/database_cleaner-1.4.1/lib/database_cleaner/configuration.rb:87:in `cleaning'
 # ./spec/rails_helper.rb:59:in `block (2 levels) in <top (required)>'

https://github.com/plataformatec/devise/blob/master/lib/devise/test_helpers.rb#L24 正在关注

def setup_controller_for_warden #:nodoc:
  @request.env['action_controller.instance'] = @controller  # ==================> L-24
end

我知道 @request 实例不适用于 :request 类型规范,因此会出现错误。

在使用 Devise 时,是否有任何帮助程序可用于在 :request type specs 中登录用户?

我发现了一个类似的问题https://github.com/plataformatec/devise/issues/1114,reply 建议如下:

如果您正在进行集成测试,请确保以传统方式登录您的用户,即填写登录表单并提交。

但我想绕过需要登录用户的规范的实际登录。

谢谢。

【问题讨论】:

【参考方案1】:

在一些 SO 帖子的帮助下(请参阅下面的 参考资料 部分),我设法实现了所需的解决方案。我在下面发布我的工作代码,以防它可以帮助其他人寻找相同的:

spec/rails_helper.rb

RSpec.configure do |config|
  ....
  ....
  config.include Devise::TestHelpers, type: :controller
  config.include Warden::Test::Helpers, type: :request
end

spec/shared_contexts.rb

RSpec.shared_context "api request global before and after hooks" do
  before(:each) do
    Warden.test_mode!
  end

  after(:each) do
    Warden.test_reset!
  end
end

RSpec.shared_context "api request authentication helper methods" do
  def sign_in(user)
    login_as(user, scope: :user)
  end

  def sign_out
    logout(:user)
  end
end

/spec/requests/api/logout_spec.rb

require 'rails_helper'
require 'shared_contexts'
      
RSpec.describe "Api Logout", :type => :request do
  include_context "api request authentication helper methods"
  include_context "api request global before and after hooks"

  let(:email)  'test_user1@example.com' 
  let(:password)  'password' 
        
  # Assumes you have FactoryGirl included in your application's test group.
  let!(:user)  create(:user, email: email, password: password) 

  context "DELETE /logout" do
    it "responds with 204 and signs out the signed-in user" do
      sign_in(user)

      # Till not figured out how to assert Warden has successfully logged in the user like we can do in a Devise controller spec by asserting subject.current_user. If anybody knows a way to do it please share.
      # expect(subject.current_user).to_not be_nil 

      delete "/logout"

      expect(response).to have_http_status(204)
    end
  end
end

我还没有弄清楚如何断言 Warden 已成功登录用户,就像我们在 Devise 控制器规范中通过断言 expect(subject.current_user).to_not be_nil 所做的那样。如果有人知道如何做到这一点,请分享。

参考文献

Integration test with rspec and devise sign_in env

https://github.com/plataformatec/devise/wiki/How-To:-Test-with-Capybara

https://github.com/hassox/warden/blob/master/lib/warden/test/helpers.rb

undefined method 'env' for nil:NilClass

https://github.com/plataformatec/devise/wiki/How-To:-Stub-authentication-in-controller-specs

以上链接中的代码仍然依赖于仅在控制器规范中可用的请求对象。因此对type: :request 规范没有用处。

https://github.com/plataformatec/devise/issues/3555

谢谢,

吉格尼什

【讨论】:

嗨 Jiggneshh Gohel 你有一些解决方案吗:expect(subject.current_user).to_not be_nil @ViktorLeonets 抱歉,我现在没有任何解决方案,因为我后来没有追求它。 谢谢!几个小时以来,我一直在寻找解决方案。我认为还有其他方法来构建它可能对我更有效(我相信使用请求规范的支持助手和宏可以达到相同的效果,就像我在其中一些示例中看到的使用控制器规范所做的那样。我仍然当我得到“未捕获的投掷:Warden”时遇到问题,但我也必须为我的功能规范解决这个问题。 我使用 `user = FactoryGirl.create(:user) login_as(user, :scope => :user, :run_callbacks => false) ` 解决了看守问题,根据 Devise “How用 Capybara 测试” 这里不工作。它抛出cannot load such file -- shared_contexts。我正在使用 rspec 3.4.4 和 rails 4.2.0。有什么帮助吗?【参考方案2】:

虽然这里的流行答案(也复制到 Devise wiki 上)是可以的,但最简单的方法是:

spec/rails_helper.rb

RSpec.configure do |config|
  # ...
  config.include Devise::Test::IntegrationHelpers, type: :request
end

只需在您的请求规范中使用sign_in。这相当于在系统/功能规范或 Rails 系统/控制器测试中声明 include Devise::Test::IntegrationHelpers

参考

Devise wiki: How to sign in and out a user in request type specs - Simple approach

【讨论】:

感谢您分享这方面的更新。如果您可以共享找到共享解决方案的 wiki 链接,将会更有帮助。我在 Devise 的 Readme 中找到了它。我还检查了模块 Devise::Test::IntegrationHelpers 只是我共享的解决方案的包装。但是仍然有一个内置的东西肯定有助于减少应用程序中自定义助手的维护。谢谢。 我得到 [#<0x00005630eaf015d8>

以上是关于devise 和 rspec-rails - 如何在请求类型规范(带有 type: :request 标记的规范)中登录用户?的主要内容,如果未能解决你的问题,请参考以下文章

Rails & Devise:如何在没有布局的情况下呈现登录页面?

ruby Rails Rspec模型使用rspec-rails,shoulda-matcher,shoulda-callbacks和factory_girl_rails测试骨架和备忘单。差不多

ruby Rails Rspec模型使用rspec-rails,shoulda-matcher,shoulda-callbacks和factory_girl_rails测试骨架和备忘单。差不多

ruby Rails Rspec模型使用rspec-rails,shoulda-matcher,shoulda-callbacks和factory_girl_rails测试骨架和备忘单。差不多

ruby Rails Rspec模型使用rspec-rails,shoulda-matcher,shoulda-callbacks和factory_girl_rails测试骨架和备忘单。差不多

如何让 Devise 和 JWT 与我的版本化 API Rails 控制器一起工作?