Greetings,
I'm trying to use the method "authenticated" when defining a route only for authenticated users and is working. I'm having something like this in my routes:
authenticated :users do
resources :client-leads, :controller => 'authenticated/coach_match_leads'
end
However, I'm trying to write test for these routes and is not working. The test is the following:
{:get => "/client-leads"}.should route_to("authenticated/coach_match_leads#index")
But I'm getting I'm exception:
NoMethodError Exception: undefined method `authenticate?' for nil:NilClass
Do you have any ideas why is this happening? Is important to notice that this is happening only for the routes defined with authenticated. All the other specs for the routes are working properly (even the ones for other devise routes).
Thanks in advanced for your help.
This is a rails limitation. the only way for you to test this is via integration tests.
Jose, is this why my functional tests are failing? I use for routes:
authenticate :admin do
namespace :admin do
resources :contracts
end
end
and getting this error when using assert_routing:
1) Error:
test: with an admin user routing should route GET /admin/contracts to/from {:action=>"index", :controller=>"admin/contracts"}. (Admin::ContractsControllerTest):
NoMethodError: undefined method `authenticate!' for nil:NilClass
(BTW, I'm using Test::Unit, not Rspec.) If this is a Rails 3/Rack limitation, I suggest adding this note in the Wiki or Readme. As always, THANKS THANKS THANKS for a brilliant library!!! :D
Could you please update the wiki yourself? Thanks!
I'd be glad to update it but first it would help to know the scope of the limitation. May I have more info or a hint at where to look in the Rails code?
As I understand, it's because Rack is first to run and checks the routes, but why is the environment information not available to Rack at that point during our functional tests? request.env['warden'] can be set before #assert_routing is called, but why is Rack unable to access it?
There is no request.env in functional tests because the functional tests are supposed to remain at the controller level. For this reason, Devise::TestHelper is forced to mock and define a warden proxy so everything works as expected.
So @request.env in Devise::TestHelper is fake so when Rack runs the real state information is of course nil. Okay, this information will help people who write controller tests, thanks.
I am having this same exact issue, but when writing cucumber integration tests.
I have this in my step definition:
Given(/^I visit my new favorite page$/) do
visit new_person_favorite_path(@person)
end
But that throws an error:
NoMethodError: undefined method `authenticate?' for nil:NilClass
from /Users/bfults/.rvm/gems/ruby-1.9.3-p448/gems/devise-2.2.4/lib/devise/rails/routes.rb:425:in `block in constraints_for'
As far as I know this should be a full integration test. Is there something else special I have to do to get capybara & cucumber to have the right warden setup in the request env?
This is weird. The warden middleware is supposed to be there, before it reaches your routes, and it should set warden in the environment.
I think my environment in the test is in a strange state. request might not be tied into Rack properly?
From: /Users/bfults/Sites/unc/uncommon/features/step_definitions/favorite_steps.rb @ line 10 :
5: fill_in 'person_password', with: @person.password
6: click_button 'Log in'
7: end
8:
9: Given(/^I visit my new favorite page$/) do
=> 10: binding.pry
11: visit new_person_favorite_path(@person)
12: end
13:
14: Given(/^I have (\d+) favorites?$/) do |num|
15: @person.favorites = []
[1] pry(#<Cucumber::Rails::World>)> request
ArgumentError: wrong number of arguments (0 for 1)
from /Users/bfults/.rvm/gems/ruby-1.9.3-p448/gems/rack-test-0.6.2/lib/rack/test.rb:121:in `request'
[2] pry(#<Cucumber::Rails::World>)> method(:request).source
=> " def \#{ali}(*args, &block)\n begin\n \#{accessor}.__send__(:\#{method}, *args, &block)\n rescue Exception\n [email protected]_if{|s| %r\"\#{Regexp.quote(__FILE__)}\"o =~ s} unless Forwardable::debug\n ::Kernel::raise\n end\n"
That is actually correct. Capybara doesnt expose a request object. Does rake middleware in test shows the warden middleware?
Yes:
$ rake middleware
use ActionDispatch::Static
use Rack::Lock
use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007fc460176f70>
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::RemoteIp
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ParamsParser
use Remotipart::Middleware
use ActionDispatch::Head
use Rack::ConditionalGet
use Rack::ETag
use ActionDispatch::BestStandardsSupport
use Warden::Manager
use Rack::Pjax
run Uncommon::Application.routes
I'm expecting that this should work:
[6] pry(#<Cucumber::Rails::World>)> Uncommon::Application.routes.recognize_path("/#{@person.slug}/favorites/new")
NoMethodError: undefined method `authenticate?' for nil:NilClass
I should back up a bit:
[1] pry(#<Cucumber::Rails::World>)> @person
=> #<Person id: 74, email: "[email protected]", role: "member", status: "active", inviter_id: nil, created_at: "2013-08-04 18:00:21", updated_at: "2013-08-04 18:00:21", encrypted_password: "$2a$04$DhJ3h6RSBOHwQIre5zvgR.YjSAe.4Xr/xtE4lxgSEuai...", reset_password_token: nil, reset_password_sent_at: nil, remember_created_at: nil, sign_in_count: 0, current_sign_in_at: nil, last_sign_in_at: nil, current_sign_in_ip: nil, last_sign_in_ip: nil, confirmation_token: nil, confirmed_at: "2013-08-04 18:00:21", confirmation_sent_at: nil, unconfirmed_email: nil, time_zone: "">
[2] pry(#<Cucumber::Rails::World>)> new_person_favorite_path(@person)
=> "/bertram-gutmann/favorites/new"
[3] pry(#<Cucumber::Rails::World>)> visit new_person_favorite_path(@person)
=> nil
But this shows in the logs:
Started GET "/bertram-gutmann/favorites/new" for 127.0.0.1 at 2013-08-04 13:00:49 -0500
Started GET "/bertram-gutmann%2Ffavorites%2Fnew" for 127.0.0.1 at 2013-08-04 13:00:49 -0500
Processing by ApplicationController#not_found as HTML
Parameters: {"nonexistent"=>"bertram-gutmann/favorites/new"}
So I assumed (maybe incorrectly) that the authenticated routes were to blame, which are set up thusly:
authenticated do
constraints(slug: Profile::SLUG_FORMAT) do
get '/:slug/favorites/new' => 'favorites#new', as: 'new_person_favorite'
end
end
# ...
get '*nonexistent' => 'application#not_found'
And this works fine in the browser, hitting a Rails server. It just fails in the cucumber test environment.
I'm experiencing the same problem. Did anyone find a workaround for it?
EDIT: I don't recommend using it!
@dpisarewski here is my workaround:
# spec/support/devise.rb
module Devise::RoutingTestHelpers
attr_accessor :request
def initialize(*args)
request
super(*args)
end
def request
@request ||= Hashie::Mash.new env: {}
end
def setup_controller(controller=subject)
request.env['action_controller.instance'] = @controller = controller
end
def sign_in!
NilClass.any_instance.unstub(:authenticate?)
NilClass.any_instance.stubs(:authenticate?).returns(true)
end
def sign_out!
NilClass.any_instance.stubs(:authenticate?).returns(false)
end
end
RSpec.configure do |config|
config.include Devise::TestHelpers, type: :controller
config.include Devise::RoutingTestHelpers, type: :routing
config.include Devise::TestHelpers, type: :routing
config.before(:each, type: :routing) do
setup_controller
sign_out!
end
end
next you use it in your routing specs like this:
# spec/routing/home_controller_spec.rb
describe HomeController do
describe 'root path' do
context 'when signed out' do
it 'routes to #landing' do
expect(get: '/').to route_to controller: 'home', action: 'landing'
end
end
context 'when signed in' do
before { sign_in! }
it 'routes to #index' do
expect(get: '/').to route_to controller: 'home', action: 'index'
end
end
end
end
@waterlink stubbing methods on NilClass is a bad idea鈥攜ou can easily break something later and your tests might be green where an object or logic was missing/bad.
@seanknox Yes, you are right. That was a very bad workaround. Stubbing methods on NilClass can be compared to switching to dark side of force. Powerful but comes with a huge price.
I highly don't recommend using my workaround from 1 year ago.
This worked for me, @seanknox.
let(:warden) do
instance_double('Warden::Proxy').tap do |warden|
allow(warden).to receive(:authenticate!).with(scope: :user)
.and_return(authenticated?)
allow(warden).to receive(:user).with(:user).and_return(user)
end
end
let(:user) { instance_double(User) }
let(:authenticated?) { true }
def simulate_running_with_devise
stub_const(
'Rack::MockRequest::DEFAULT_ENV',
Rack::MockRequest::DEFAULT_ENV.merge('warden' => warden),
)
end
I had to change ":authenticate!" in @ClayShentrup 's answer to ":authenticate?" to get it working, like this:
let(:warden) do
instance_double('Warden::Proxy').tap do |warden|
allow(warden).to receive(:authenticate?).with(scope: :user)
.and_return(authenticated?)
allow(warden).to receive(:user).with(:user).and_return(user)
end
end
let(:user) { instance_double(User) }
let(:authenticated?) { true }
def simulate_running_with_devise
stub_const(
'Rack::MockRequest::DEFAULT_ENV',
Rack::MockRequest::DEFAULT_ENV.merge('warden' => warden),
)
end
this kinda helped me:
module DeviseRoutingHelpers
# workaround for this issue https://github.com/plataformatec/devise/issues/1670
def mock_warden_for_route_tests!
warden = double
allow_any_instance_of(ActionDispatch::Request).to receive(:env).
and_wrap_original { |orig, *args|
env = orig.call(*args)
env['warden'] = warden
env
}
allow(warden).to receive(:authenticate!).and_return(true)
end
end
RSpec.configure do |config|
config.include DeviseRoutingHelpers, type: :routing
config.before(:each, type: :routing) do
mock_warden_for_route_tests!
end
end
Most helpful comment
this kinda helped me: