问题
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 => "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
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.
回答1:
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
回答2:
Add this line to your routes.rb
# Devise routes
devise_for :users # Or the name of your custom model
回答3:
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.
回答4:
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
回答5:
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
回答6:
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
回答7:
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 :-)
回答8:
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 :)
回答9:
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.
来源:https://stackoverflow.com/questions/6363471/could-not-find-a-valid-mapping-for-user-only-on-second-and-successive-t