Controller test helpers for Warden

Restful-authentication, Authlogic, Device, Sorcery... I was tired of wandering authentication libraries for Rails.

At the end, I settled into Warden.

Warden gives us full flexibility. It's far more flexible than Sorcery or Authlogic, actually to use Warden is more like writing authentication from scratch. And, we can access authentication in routes file as Devise.

But Warden had lacked one thing: test helpers for controller testing.

So, I wrote controller test helpers for Warden.
Most of the code was extracted from Devise's Devise::TestHelpers.
I cut off Devise related code and wrote some code for loading Rails application config.

With this test helpers, you can access to warden in your controller test, and you can do such as warden.set_user(user) to login, and warden.user to get current user, etc.

# test/test_helper.rb

require 'test_helpers/warden.rb'

class ActionController::TestCase
  include Warden::Test::ControllerHelpers
end
# test/test_helpers/warden.rb

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

Tags: Rails Warden testing

By Kentaro Imai on June 22nd, 2012

comments powered by Disqus