“无法找到 #的有效映射”仅在第二次和后续测试中

发布于 2024-11-15 12:35:55 字数 5023 浏览 2 评论 0原文

我正在尝试编写一个请求测试,断言根据用户是否登录或注销,应用程序布局上会显示正确的链接。 FWIW,我正在使用 Devise 进行认证。

这是我的规范:

require 'spec_helper'
require 'devise/test_helpers'

describe "Layout Links" do
  context "the home page" do
    context "session controls" do
      context "for an authenticated user" do
        before do
          # I know these should all operate in isolation, but I
          # want to make sure the user is explicitly logged out
          visit destroy_user_session_path

          @user = Factory(:user, :password => "Asd123", :password_confirmation => "Asd123")
          @user.confirm!

          # I tried adding this per the Devise wiki, but no change
          @request.env["devise.mapping"] = Devise.mappings[:user]

          # Now log a new user in
          visit new_user_session_path
          fill_in "Email",    :with => @user.email
          fill_in "Password", :with => "Asd123"
          click_button "Sign in"
          get '/'
        end

        it "should not have a link to the sign in page" do
          response.should_not have_selector(
            '#session a',
            :href => new_user_session_path
          )
        end

        it "should not have a link to registration page" do
          response.should_not have_selector(
            '#session a',
            :href => new_user_registration_path
          )
        end

        it "should have a link to the edit profile page" do
          response.should have_selector(
            '#session a',
            :content => "My Profile",
            :href => edit_user_registration_path
          )
        end

        it "should have a link to sign out page" do
          response.should have_selector(
            '#session a',
            :content => "Logout",
            :href => destroy_user_session_path
          )
        end
      end # context "for an authenticated user"
    end # context "session controls"
  end
end

第一个测试通过,但最后三个测试全部失败,并出现错误,

Failure/Error: @user = Factory(:user, :password => "Asd123", :password_confirmation => "Asd123")
  RuntimeError:
    Could not find a valid mapping for #<User id: xxx, ...>

我通过 Devise wiki、Google 群组和搜索结果搜索了原因,但我发现的只是未解答的问题或设置 的建议config.include Devise::TestHelpers, :type => :controller 但这仅适用于控制器测试,不适用于请求测试。


更新

我已经进行了更多故障排除,但无法弄清楚最终触发问题的原因。看看下面的代码。

首先,对于一些上下文,这里是用户工厂声明。它在单元测试中运行良好。

# spec/factories.rb
Factory.define :user do |f|
  f.email { Faker::Internet.email }
  f.email_confirmation { |f| f.email }
  f.password "AbcD3fG"
  f.password_confirmation "AbcD3fG"
  f.remember_me { (Random.new.rand(0..1) == 1) ? true : false }
end

现在,考虑以下集成测试

# spec/requests/user_links_spec.rb
require "spec_helper"

describe "User Links" do
  before(:each) do
    # This doesn't trigger the problem
    # @user = nil

    # This doesn't trigger the problem
    # @user = User.new

    # This doesn't trigger the problem
    # @user = User.create(
    #   :email => "[email protected]", 
    #   :email_confirmation => "[email protected]", 
    #   :password => "asdf1234", 
    #   :password_confirmation => "asdf1234"
    # )

    # This doesn't trigger the problem
    # @user = User.new
    # @user.email = Faker::Internet.email
    # @user.email_confirmation = @user.email
    # @user.password = "AbcD3fG"
    # @user.password_confirmation = "AbcD3fG"
    # @user.remember_me = (Random.new.rand(0..1) == 1) ? true : false
    # @user.save!

    # This triggers the problem!
    @user = Factory(:user)

    # This doesn't trigger the same problem, but it raises a ActiveRecord::AssociationTypeMismatch error instead. Still no idea why. It was working fine before in other request tests.
    # @user = Factory(:brand)
  end

  context "when using `@user = Factory(:user)` in the setup: " do
    2.times do |i|
      it "this should pass on the 1st iteration, but not the 2nd (iteration ##{i+1})" do
        # This doesn't trigger an error
        true.should_not eql(false)
      end

      it "this should pass on the 1st iteration, but trigger the error that causes all successive test cases to fail (iteration ##{i+1})" do
        # Every test case after this will be borken!
        get '/'
      end

      it "this will fail on all iterations (iteration ##{i+1})" do
        # This will now trigger an error
        true.should_not eql(false)
      end
    end
  end
end

如果我们注释掉 get '/' 位或将其替换为其他内容(或什么都不做),则测试都会正常运行。

所以,我不知道这是一个factory_girl问题(我倾向于怀疑它,因为我可以在其他地方使用用户工厂而不会出现问题)还是一个Devise问题(在我的应用程序中设置该gem后我开始收到这些错误,但我也只有一个其他请求测试,它工作得很好,但现在得到了 AssociationTypeMismatch 错误;相关性 ≠ 因果关系...)或 RSpec 问题或其他一些奇怪的边缘情况宝石冲突。

I'm trying to write a request test that asserts that the proper links appear on the application layout depending in whether a user is logged in or out. FWIW, I'm using Devise for the authentication piece.

Here's my spec:

require 'spec_helper'
require 'devise/test_helpers'

describe "Layout Links" do
  context "the home page" do
    context "session controls" do
      context "for an authenticated user" do
        before do
          # I know these should all operate in isolation, but I
          # want to make sure the user is explicitly logged out
          visit destroy_user_session_path

          @user = Factory(:user, :password => "Asd123", :password_confirmation => "Asd123")
          @user.confirm!

          # I tried adding this per the Devise wiki, but no change
          @request.env["devise.mapping"] = Devise.mappings[:user]

          # Now log a new user in
          visit new_user_session_path
          fill_in "Email",    :with => @user.email
          fill_in "Password", :with => "Asd123"
          click_button "Sign in"
          get '/'
        end

        it "should not have a link to the sign in page" do
          response.should_not have_selector(
            '#session a',
            :href => new_user_session_path
          )
        end

        it "should not have a link to registration page" do
          response.should_not have_selector(
            '#session a',
            :href => new_user_registration_path
          )
        end

        it "should have a link to the edit profile page" do
          response.should have_selector(
            '#session a',
            :content => "My Profile",
            :href => edit_user_registration_path
          )
        end

        it "should have a link to sign out page" do
          response.should have_selector(
            '#session a',
            :content => "Logout",
            :href => destroy_user_session_path
          )
        end
      end # context "for an authenticated user"
    end # context "session controls"
  end
end

The first test passes, but the last three all fail with the error

Failure/Error: @user = Factory(:user, :password => "Asd123", :password_confirmation => "Asd123")
  RuntimeError:
    Could not find a valid mapping for #<User id: xxx, ...>

I've searched through the Devise wiki, Google group and search results for a cause, but all I find are unanswered questions or suggestions to set config.include Devise::TestHelpers, :type => :controller but that only applies to controller tests, not request test.


Update

I've done some more troubleshooting and I can't make heads or tails of what is ultimately triggering the problem. Have a look at the following code.

First, for some context here is the User factory declaration. It works fine in unit tests.

# spec/factories.rb
Factory.define :user do |f|
  f.email { Faker::Internet.email }
  f.email_confirmation { |f| f.email }
  f.password "AbcD3fG"
  f.password_confirmation "AbcD3fG"
  f.remember_me { (Random.new.rand(0..1) == 1) ? true : false }
end

Now, consider the following integration test

# spec/requests/user_links_spec.rb
require "spec_helper"

describe "User Links" do
  before(:each) do
    # This doesn't trigger the problem
    # @user = nil

    # This doesn't trigger the problem
    # @user = User.new

    # This doesn't trigger the problem
    # @user = User.create(
    #   :email => "[email protected]", 
    #   :email_confirmation => "[email protected]", 
    #   :password => "asdf1234", 
    #   :password_confirmation => "asdf1234"
    # )

    # This doesn't trigger the problem
    # @user = User.new
    # @user.email = Faker::Internet.email
    # @user.email_confirmation = @user.email
    # @user.password = "AbcD3fG"
    # @user.password_confirmation = "AbcD3fG"
    # @user.remember_me = (Random.new.rand(0..1) == 1) ? true : false
    # @user.save!

    # This triggers the problem!
    @user = Factory(:user)

    # This doesn't trigger the same problem, but it raises a ActiveRecord::AssociationTypeMismatch error instead. Still no idea why. It was working fine before in other request tests.
    # @user = Factory(:brand)
  end

  context "when using `@user = Factory(:user)` in the setup: " do
    2.times do |i|
      it "this should pass on the 1st iteration, but not the 2nd (iteration ##{i+1})" do
        # This doesn't trigger an error
        true.should_not eql(false)
      end

      it "this should pass on the 1st iteration, but trigger the error that causes all successive test cases to fail (iteration ##{i+1})" do
        # Every test case after this will be borken!
        get '/'
      end

      it "this will fail on all iterations (iteration ##{i+1})" do
        # This will now trigger an error
        true.should_not eql(false)
      end
    end
  end
end

If we comment out or replace the get '/' bit with anything else (or nothing at all), the tests all run fine.

So, I don't know if this is a factory_girl issue (I tend to doubt it since I can use User factories elsewhere w/o issue) or a Devise issue (I started getting these errors after setting up that gem in my application, but I also only had one other request test which did work fine but is now getting that AssociationTypeMismatch error; correlation ≠ causation...) or an RSpec issue or some other weird edge-case gem conflict.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

凉宸 2024-11-22 12:35:56

感谢: http ://blog.thefrontiergroup.com.au/2011/03/reloading-factory-girl-factories-in-the-rails-3-console/

“Devise 使用类和路由之间的映射,因此当工厂构建的对象在控制台重新加载或类重新定义后到达 Devise 时,它​​将失败。”

将其放入初始值设定项或 application.rb 中

ActionDispatch::Callbacks.after do
  # Reload the factories
  return unless (Rails.env.development? || Rails.env.test?)

  unless FactoryGirl.factories.blank? # first init will load factories, this should only run on subsequent reloads
    FactoryGirl.factories.clear
    FactoryGirl.find_definitions
  end
end

Thanks to : http://blog.thefrontiergroup.com.au/2011/03/reloading-factory-girl-factories-in-the-rails-3-console/

"Devise uses a mapping between classes and routes, so when a factory built object comes through to Devise after a console reload, or a class redefinition then it will fail."

Put this in an initializer or application.rb

ActionDispatch::Callbacks.after do
  # Reload the factories
  return unless (Rails.env.development? || Rails.env.test?)

  unless FactoryGirl.factories.blank? # first init will load factories, this should only run on subsequent reloads
    FactoryGirl.factories.clear
    FactoryGirl.find_definitions
  end
end
携余温的黄昏 2024-11-22 12:35:56

对于未来的读者:我收到了同样的错误,但原因不同。

我们的应用程序有多个用户模型,其中一个派生自另一个模型。

为了论证:

class SimpleUser

class User < SimpleUser

在我的控制器中,我使用了对 SimpleUser (父类)的引用,但 Devise 配置为使用 User (子类)。

在调用 Devise::Mapping.find_scope 期间!它确实是.is_a?与对象引用和配置的类进行比较。

由于我的引用是 SimpleUser,并且配置的类是 User,因此 .is_a?失败,因为比较询问父类是否 is_a?子类,这总是错误的。

希望对其他人有帮助。

For future readers: I received same error, but for a different reason.

Our app had multiple user models where one derived from the other

For arguments sake:

class SimpleUser

class User < SimpleUser

In my controller I used a reference to SimpleUser (the parent class), but Devise was configured to use User (the child class).

During an invocation of Devise::Mapping.find_scope! it does .is_a? comparison against the object reference and the configured class.

Since my reference was a SimpleUser, and the configured class was User, the .is_a? fails because the comparison was asking if the Parent class is_a? Child class, which is always false.

Hope that helps someone else.

╭ゆ眷念 2024-11-22 12:35:56

我的路由文件中出现此错误,因为我有一个 API 端点,我希望允许用户从中重置密码,但我希望用户实际上在 Web 视图上进行密码更改,该视图没有在 /api

这就是我修复路由以使其工作的方法:

CoolApp::Application.routes.draw do
  namespace :api, defaults: { format: :json } do
    devise_scope :users do
      post 'users/passwords', to: 'passwords#create'
    end

    resources :users, only: [:create, :update]
  end

  devise_for :users

  root to: 'high_voltage/pages#show', id: 'home'
end

I had this error in my routes file because I had an API endpoint that I wanted to allow users to reset their passwords from, but I wanted the users to actually do the password change on the web view, which wasn't namespaced under /api

This is how I fixed the routes to make it work:

CoolApp::Application.routes.draw do
  namespace :api, defaults: { format: :json } do
    devise_scope :users do
      post 'users/passwords', to: 'passwords#create'
    end

    resources :users, only: [:create, :update]
  end

  devise_for :users

  root to: 'high_voltage/pages#show', id: 'home'
end
未央 2024-11-22 12:35:56

Halfnelson 的解决方案也对我有用,但由于我使用 Fabrication gem 而不是 FactoryGirl,我需要进行一些调整。这是我放入初始化程序中的代码:

if Rails.env.development? || Rails.env.test?
  ActionDispatch::Callbacks.after do
    Fabrication.clear_definitions
    Rails.logger.debug 'Reloading fabricators'
  end
end

Halfnelson's solution worked for me too, but since I'm using the Fabrication gem instead of FactoryGirl, I needed to make some adjustments. Here's the code I droped into a initializer:

if Rails.env.development? || Rails.env.test?
  ActionDispatch::Callbacks.after do
    Fabrication.clear_definitions
    Rails.logger.debug 'Reloading fabricators'
  end
end
ˉ厌 2024-11-22 12:35:56

我知道这是一个旧线程,但我想为其他遇到此问题的人提供答案。不知道我在哪里读到这篇文章,但支持在另一个论坛上发布此内容的人。

总而言之,设计测试助手不能很好地与集成测试配合使用。

因此,从规范本身中删除所有对 Devise::TestHelpers 的引用(有些人使用 include,其他人使用 require 'devise/testhelpers')。

然后在spec_helper文件中添加:

RSpec.configure do |config|
  config.include Devise::TestHelpers, :type => :controller
end

i know this is an old thread, but i wanted an answer to whoever else runs into this. not sure where i read this, but props to the person who posted about this on another forum.

Long and the short is that the devise test helpers don't work well with integration tests.

So remove all references for Devise::TestHelpers (some people use include, other people use require 'devise/testhelpers') from within the specs themselves.

then in the spec_helper file add:

RSpec.configure do |config|
  config.include Devise::TestHelpers, :type => :controller
end
清风挽心 2024-11-22 12:35:56

就我而言,这是 Devis 的 confirm! 方法的问题。因此,我没有这样做(Minitest::Test 代码):

setup do
  @admin = create(:admin).confirm!
end

我已经这样做了:

setup do
  @admin = create(:admin)
  @admin.confirm!
end

并且它有效:)

In my case this was a problem with Devis's confirm! method. So instead of doing this (Minitest::Test code):

setup do
  @admin = create(:admin).confirm!
end

I have done this:

setup do
  @admin = create(:admin)
  @admin.confirm!
end

And it worked :)

骄兵必败 2024-11-22 12:35:56

以防万一其他人遇到这个问题,出于与我相同的原因 - 在更改一些配置文件后,我在基本上所有测试中都遇到了同样的问题 - 事实证明这是由于 RAILS_ENV 被当我意外运行测试时设置为 development。在添加特定于测试的 Rails 初始化程序之前可能值得检查:-)

Just in case anyone else runs into this, for the same reason as me - I had the same problem on basically all my tests after a change to some config files - it turned out it was due to RAILS_ENV being set to development when I ran the tests by accident. Might be worth checking before adding a test-specific rails initializer :-)

╰◇生如夏花灿烂 2024-11-22 12:35:56

当我以测试用户身份登录我的一个应用程序并进行一些测试上传和测试帖子时,我以前就遇到过这种情况。然后我删除了该测试用户,当我尝试使用同一测试用户再次注册时,我显示消息“无法找到 nil 的有效映射”。我解决这个问题的方法是删除我作为测试用户所做的所有测试上传和测试帖子。然后我尝试再次注册,结果成功了。通过这种方式,删除内容的更快、更简单的方法是使用 sqlite 数据库浏览器

I had this happen to me before when I signed in as a test user on one of my apps and had made some test uploads and test posts. I then deleted that test user and when I tried to sign up again with the same test user I was displayed the message "Could not find a valid mapping for nil". What I did to solve it was to delete all of the test uploads and test post that I had made as that test user. I then tried to sign up again and it worked. By that way a faster and easier way to delete stuff is by using db browser for sqlite.

空城缀染半城烟沙 2024-11-22 12:35:55

将此行添加到您的 routes.rb

# Devise routes
devise_for :users # Or the name of your custom model

Add this line to your routes.rb

# Devise routes
devise_for :users # Or the name of your custom model
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文