Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Configuring Warden for use in RSpec controller specs

Tags:

I was able to use Devise's sign_in method to log in a user in my controller specs. But now that I'm removing Devise from my application, I'm not quite sure how to get similar functionality working with just Warden on its own.

How should I go about setting up spec/spec_helper.rb and related spec/support/*.rb files to get Warden running within controller specs sufficiently?

I've tried setting up a file at spec/support/warden.rb with these contents:

RSpec.configure do |config|   config.include Warden::Test::Helpers    config.after do     Warden.test_reset!   end end 

Then I have before calls similar to this to authenticate a user factory:

before { login_as FactoryGirl.create(:user) } 

But here is the error that I keep seeing:

NameError:   undefined method `user' for nil:NilClass 

This error traces back to my authenticate_user! method in the controller:

def authenticate_user!   redirect_to login_path, notice: "You need to sign in or sign up before continuing." if env['warden'].user.nil? end 

I'd appreciate any guidance that anyone could provide.

like image 896
Chris Peters Avatar asked Nov 16 '12 16:11

Chris Peters


Video Answer


2 Answers

I didn't think that this question applied to my situation, but it does: Stubbing Warden on Controller Tests

As it turns out, Warden does not get included into RSpec controller specs, so you need to do some magic to finagle it in.

Kentaro Imai's Controller test helpers for Warden blog post was particularly helpful. Here's how I got it working for RSpec.

Step 1: Create spec/spec_helper/warden.rb and paste in these contents, which Kentaro derived from Devise:

module Warden   # Warden::Test::ControllerHelpers provides a facility to test controllers in isolation   # Most of the code was extracted from Devise's Devise::TestHelpers.   module Test     module ControllerHelpers       def self.included(base)         base.class_eval do           setup :setup_controller_for_warden, :warden if respond_to?(:setup)         end       end        # Override process to consider warden.       def process(*)         # Make sure we always return @response, a la ActionController::TestCase::Behavior#process, even if warden interrupts         _catch_warden {super} || @response       end        # We need to setup the environment variables and the response in the controller       def setup_controller_for_warden         @request.env['action_controller.instance'] = @controller       end        # Quick access to Warden::Proxy.       def warden         @warden ||= begin           manager = Warden::Manager.new(nil, &Rails.application.config.middleware.detect{|m| m.name == 'Warden::Manager'}.block)           @request.env['warden'] = Warden::Proxy.new(@request.env, manager)         end       end        protected        # Catch warden continuations and handle like the middleware would.       # Returns nil when interrupted, otherwise the normal result of the block.       def _catch_warden(&block)         result = catch(:warden, &block)          if result.is_a?(Hash) && !warden.custom_failure? && [email protected](:performed?)           result[:action] ||= :unauthenticated            env = @controller.request.env           env['PATH_INFO'] = "/#{result[:action]}"           env['warden.options'] = result           Warden::Manager._run_callbacks(:before_failure, env, result)            status, headers, body = warden.config[:failure_app].call(env).to_a           @controller.send :render, :status => status, :text => body,             :content_type => headers['Content-Type'], :location => headers['Location']            nil         else           result         end       end     end   end end 

Step 2: In spec/spec_helper.rb, within the RSpec.configure block, add this line to include the new module:

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

Step 3: To log in a user in a before block, use syntax similar to this:

before { warden.set_user FactoryGirl.create(:user) } 

Step 4: Make sure that you reference request.env['warden'] in your controllers, not env['warden']. The latter will not work in controller specs in the test environment.

Hat tip to Kentaro Imai, whom I owe a beer one day (or in another life)!

like image 110
Chris Peters Avatar answered Sep 21 '22 07:09

Chris Peters


There is a basic problem with what you are trying to do. Warden is a Rack middleware, but RSpec controller specs don't even include Rack, as these types of specs are not meant to run your full application stack, but only your controller code. You can test your middleware with separate tests just for those, but in this case, I don't think it makes sense to test that Warden itself works.

To test that you have Warden configured correctly, you should use request specs or integration specs (cucumber, capybara or similar).

Although it is technically possible to mock out Warden in controller specs, I think it is not providing you much benefit while increasing the complexity of your test code significantly. Keep in mind that Rack middleware is meant to operate in a transparent way so that it is easy to swap middleware in and out as you like. Your controller should not be directly dependent on Warden at all (except perhaps for ApplicationController), actually, so having a test dependency on Warden for your controller is a sign of broken encapsulation.

I ran into this same issue recently so I hope this comment will be useful.

like image 42
bowsersenior Avatar answered Sep 17 '22 07:09

bowsersenior