Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
675 views
in Technique[技术] by (71.8m points)

ruby on rails - Configuring Warden for use in RSpec controller specs

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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? && !@controller.send(: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)!


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...