在第二次及以后的测试中出现 "#<User ...>" 的有效映射未找到。

31
我将尝试编写一个请求测试,以验证应用程序布局上出现的链接是否取决于用户登录或注销。值得一提的是,我正在使用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维基、Google群组和搜索结果中搜索了很久,但找到的都是未解答的问题或建议设置 config.include Devise::TestHelpers, :type => :controller ,但这仅适用于控制器测试,而不是请求测试。


更新

我已经进行了更多故障排除,但最终触发问题的原因让我无法理解。请查看以下代码。

首先,为了一些上下文信息,这是User工厂声明。它在单元测试中运行良好。

# 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 => "foo@bar.co", 
    #   :email_confirmation => "foo@bar.co", 
    #   :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问题(我倾向于怀疑,因为我可以在其他地方使用User工厂而没有问题)还是一个Devise问题(在设置了该gem之后,我开始遇到这些错误,但我也只有另外一个请求测试,在此之前一切正常,但现在也出现了AssociationTypeMismatch错误;相关性≠因果关系...)或者是一个RSpec问题,或者是其他奇怪的边缘情况gem冲突。

我发现即使用户在任何测试用例中都没有被使用,这种情况仍然会发生。由于更新内容太大无法在评论表单中发布,因此我将在工单中进行更新。 - Chris Bloom
我在Devise邮件组中发现了一个讨论同样问题的帖子,但目前那里也没有答案。http://groups.google.com/group/plataformatec-devise/browse_thread/thread/4ada5b12c0c279cd/053ee22cdcf658d1 - Chris Bloom
9个回答

25
添加此行到你的routes.rb文件中。
# Devise routes
devise_for :users # Or the name of your custom model

9
感谢: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

有时可能需要使用 ActionDispatch::Callbacks.before,我的情况是:https://dev59.com/WZbfa4cB1Zd3GeqPwaFq#37003351 - jmarceli

9

未来的读者注意:我也收到了相同的错误信息,但原因不同。

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

为了举例说明:

class SimpleUser

class User < SimpleUser

在我的控制器中,我使用了SimpleUser(父类)的引用,但是Devise被配置为使用User(子类)。
在调用Devise :: Mapping.find_scope!期间,它会针对对象引用和配置的类进行.is_a?比较。
由于我的引用是SimpleUser,而配置的类是User,所以.is_a?失败了,因为比较是在问父类是否是一个子类,这总是错误的。
希望这可以帮助其他人。

那么,你对这个问题有什么解决方案? - dennismonsewicz
1
如果你看到和我一样的错误,请确保你使用的类是Devise配置要使用的相同类(或者一个子类也可以)。在我的情况下,错误是因为我试图使用Devise配置要使用的类的父类。 - Alex Soto

4
我在路由文件中遇到了这个错误,因为我有一个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

2
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

1

我知道这是一个旧的帖子,但我想要一个答案,以便其他人遇到此问题时可以参考。不确定我在哪里读到的,但感谢在另一个论坛上发布了关于此问题的人。

长话短说,设备测试助手与集成测试不兼容。

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

然后在spec_helper文件中添加:

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

1
正如我在上面的原始帖子中所指出的那样,那只适用于控制器测试,而不是我尝试的请求测试。你是说你不能在集成测试中使用Devise,只能在控制器测试中使用吗? - Chris Bloom

1

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


1
在我的情况下,这是Devis的confirm!方法出现了问题。所以,不要像这样做(Minitest :: Test代码):
setup do
  @admin = create(:admin).confirm!
end

我已经完成了这个:
setup do
  @admin = create(:admin)
  @admin.confirm!
end

它已经工作了 :)


0

我之前也遇到过这种情况,当我以测试用户身份登录我的某个应用程序并进行了一些测试上传和测试发布时。然后我删除了该测试用户,当我尝试再次使用相同的测试用户注册时,显示了消息“找不到nil的有效映射”。我解决它的方法是删除我作为该测试用户所做的所有测试上传和测试发布。然后我尝试重新注册,它就可以工作了。通过使用db browser for sqlite,更快、更简单地删除东西。


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