40

I try to use Devise for my Rails app. I can sign up and login but when I go to my other page "build" I get the following error:

Devise::MissingWarden in Home#show Devise could not find the Warden::Proxy instance on your request environment. Make sure that your application is loading Devise and Warden as expected and that the Warden::Manager middleware is present in your middleware stack. If you are seeing this on one of your tests, ensure that your tests are either executing the Rails middleware stack or that your tests are using the Devise::Test::ControllerHelpers module to inject the request.env['warden'] object for you.

Here is my controller:

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  private
  # Overwriting the sign_out redirect path method
  def after_sign_out_path_for(resource_or_scope)
    build_path
  end
end

Here rea my two partial views:

<!-- views/devise/menu/_login_items.html.erb -->
<% if user_signed_in? %>
  <li>
  <%= link_to('Logout', destroy_user_session_path, :method => :delete) %>
  </li>
<% else %>
  <li>
  <%= link_to('Login', new_user_session_path)  %>
  </li>
<% end %>

and

<!-- views/devise/menu/_registration_items.html.erb -->
<% if user_signed_in? %>
  <li>
  <%= link_to('Edit registration', edit_user_registration_path) %>
  </li>
<% else %>
  <li>
  <%= link_to('Register', new_user_registration_path)  %>
  </li>
<% end %>

After debugging, I figured out that the problem is coming from this line in my "show" controller: template = HomeController.render('layouts/_template')

Thanks for your help.

nico_lrx
  • 715
  • 1
  • 19
  • 36
  • 1
    Is this happening when you run the app locally, or when you run your test suite? – Alex Schaefer Apr 15 '17 at 16:24
  • According to this answer, you cannot use Warden/Devise in a controller test (`ActionController::TestCase`) because it is Rack middleware and the Rack middleware is not loaded for controller tests. https://stackoverflow.com/questions/13420923/configuring-warden-for-use-in-rspec-controller-specs/17050993#17050993 – Chloe Sep 19 '18 at 22:03
  • And maybe you can post the fix? – NicuVlad Dec 01 '18 at 10:05

8 Answers8

57

Based on this SO answer you need to include the Devise::Test::ControllerHelpers module in your controller specs. Add the following to your rails_helper:

RSpec.configure do |config|
  config.include Devise::Test::ControllerHelpers, type: :controller
end
Community
  • 1
  • 1
Obromios
  • 15,408
  • 15
  • 72
  • 127
  • After debugging, I figured out that the problem is coming from this line in my "show" controller: template = HomeController.render('layouts/_template') – nico_lrx Jan 29 '17 at 11:55
  • 17
    I hate it when the accepted answer has nothing to do with the original question. The original post said nothing about specs, rspec or testing in general. This is not the correct answer. – Midwire Nov 13 '20 at 20:08
30

It can happen when you call Devise methods, like current_user, in code being managed by action cable. like a background job to render comment or something else.

You may resolve it by doing something like the following in your controller. It propagates warden to the the @env['warden'] variable so that Devise can use it:

def create
  @product = Product.find(comment_params[:product_id])
  @comment = @product.comments.build(comment_params)
  @comment.save!

  gon.comment_id = @comment.id
  gon.comment_user_id = @comment.user_id

  ActionCable.server.broadcast "chat", comment: render_comment
  
  render :create, layout: false
end


def render_comment
  CommentsController.renderer.instance_variable_set(
    :@env, {
      "HTTP_HOST"=>"localhost:3000", 
      "HTTPS"=>"off", 
      "REQUEST_METHOD"=>"GET", 
      "SCRIPT_NAME"=>"",   
      "warden" => warden
    }
  )

  CommentsController.render(
    partial: 'comments/comment_detail',
    locals: {
      product: @product,
      comment: @comment
    }
  )
end

This will help you resolve the warden issue, if you have used Devise's current_user in that partial, it will give you the commentor user (as it should since that user initiated the rendering of partial).

Now to solve this, if you have a front end framework you might need to fetch the current user from cookies in order to restrict some actions like edit/delete. but if you are working in pure rails the solution I came across is that you have to make a hidden field in the dom having current users id, and you will fetch that id for comparison in a script. you might need to access rails variables in javascript, for that you can use GON gem.

I know this answer might contain much more than asked but I've searched alot and no where I found a satisfactory solution to this problem, feel free to discuss.

Midwire
  • 1,090
  • 8
  • 25
Fazal Karim
  • 528
  • 6
  • 12
  • This solution worked great for me in a similar context (not in specs as in the accepted answer). I've found an easier way to inject `warden` though, you can just do `CommentsController.renderer.new(warden: warden).render(...)`. – hattila91 Dec 08 '22 at 13:47
11

Add

config.include Devise::Test::ControllerHelpers, type: :controller

in your rails_helper.rb file.

Neeraj Kumar
  • 6,045
  • 2
  • 31
  • 23
9

I had a similar problem when testing views where 'current_user' was present in some conditional.

I solved it with this:

let(:user) { FactoryGirl.create :customer }

before(:each) do
  allow(view).to receive(:current_user).and_return(user)
  ...
  • This is good - but you'll want to update `FactoryGirl` to `FactoryBot` as the gem has changed name since you left this answer. – charlesdeb Nov 07 '22 at 18:13
3

If you are having this problem outside of Rspec say you were trying to render a view within an ActionCable channel, a scalable/maintainable approach is to use a helper or a view library like ViewComponents/cells. This way, you can extract your existing view into a component and parameterize any devise/warden methods called in the view. The advantage of this approach is

  1. The view is easier to test
  2. You can call it from anywhere in your code
  3. There are no coupled dependencies

Example using view helpers

Say you have in app/views/messages/show.html.erb the following

<%= current_user.first_name %>
<%= @message %>

Calling MessagesController.render :show outside the controller will cause an error since access to the request object is not available. Using ViewComponents, we extract the view into its own component

in app/components/message_component.rb

class MessageComponent < ViewComponent::Base
  def initialize(user:, message:)
    @user = user
    @message = message
  end
end

in app/components/message_component.html.erb

<%= @user.first_name %>
<%= @message %>

Usage

In app/views/messages/show.html.erb just call

<%= render(MessageComponent.new(message: @message, user: current_user) %>

In ActionCable, call it like so

ApplicationController.render(MessageComponent.new(message: @message, user: current_user)

Since you have access to ActiveRecord models from anywhere in your code, you should be able to fetch @message

theterminalguy
  • 1,842
  • 18
  • 20
2

Well, i have faced the same issue "devise could not find the warden :: proxy in actioncable" It can be resolved by passing the current user_id to your perform method in your job as you might be accessing the current user in your view which you rendering through broadcast and due to this issue was caused

Aniruddh Parihar
  • 3,072
  • 3
  • 21
  • 39
2

I have to use the current_user in the helper method, which was used in view of which I was writing specs.

let(:user) { create(:user) }
before do
 allow(helper).to receive(:current_user).and_return(user)
end

but if you are working on view specs then do this

let(:user) { create(:user) }
before do
 allow(view).to receive(:current_user).and_return(user)
end
Taimoor Changaiz
  • 10,250
  • 4
  • 49
  • 53
-1

I had this error and could not find answer anywhere else. It was after dropping and creating the db. But I didn't load the schema.

This fixed it:

rake db:drop db:create db:schema:load
A B
  • 7
  • 1