module Devise::Test::ControllerHelpers

`Devise::Test::ControllerHelpers` provides a facility to test controllers in isolation when using `ActionController::TestCase` allowing you to quickly #sign_in or #sign_out a user. Do not use `Devise::Test::ControllerHelpers` in integration tests.

Examples

class PostsTest < ActionController::TestCase
  include Devise::Test::ControllerHelpers

  test 'authenticated users can GET index' do
    sign_in users(:bob)

    get :index
    assert_response :success
  end
end

Important: you should not test Warden specific behavior (like callbacks) using `Devise::Test::ControllerHelpers` since it is a stub of the actual behavior. Such callbacks should be tested in your integration suite instead.

Public Instance Methods

process(*) click to toggle source

Override process to consider warden.

Calls superclass method
# File lib/devise/test/controller_helpers.rb, line 33
def process(*)
  _catch_warden { super }

  @response
end
sign_in(resource, deprecated = nil, scope: nil) click to toggle source

#sign_in a given resource by storing its keys in the session. This method bypass any warden authentication callback.

  • resource - The resource that should be authenticated

  • scope - An optional Symbol with the scope where the resource

    should be signed in with.

Examples:

#sign_in users(:alice) #sign_in users(:alice), scope: :admin

# File lib/devise/test/controller_helpers.rb, line 64
      def sign_in(resource, deprecated = nil, scope: nil)
        if deprecated.present?
          scope = resource
          resource = deprecated

          ActiveSupport::Deprecation.warn "            [Devise] sign_in(:#{scope}, resource) on controller tests is deprecated and will be removed from Devise.
            Please use sign_in(resource, scope: :#{scope}) instead.
".strip_heredoc
        end

        scope ||= Devise::Mapping.find_scope!(resource)

        warden.instance_variable_get(:@users).delete(scope)
        warden.session_serializer.store(resource, scope)
      end
sign_out(resource_or_scope) click to toggle source

Sign out a given resource or scope by calling logout on Warden. This method bypass any warden logout callback.

Examples:

sign_out :user     # sign_out(scope)
sign_out @user     # sign_out(resource)
# File lib/devise/test/controller_helpers.rb, line 89
def sign_out(resource_or_scope)
  scope = Devise::Mapping.find_scope!(resource_or_scope)
  @controller.instance_variable_set(:"@current_#{scope}", nil)
  user = warden.instance_variable_get(:@users).delete(scope)
  warden.session_serializer.delete(scope, user)
end

Protected Instance Methods

_catch_warden(&block) click to toggle source

Catch warden continuations and handle like the middleware would. Returns nil when interrupted, otherwise the normal result of the block.

# File lib/devise/test/controller_helpers.rb, line 100
def _catch_warden(&block)
  result = catch(:warden, &block)

  env = @controller.request.env

  result ||= {}

  # Set the response. In production, the rack result is returned
  # from Warden::Manager#call, which the following is modelled on.
  case result
  when Array
    if result.first == 401 && intercept_401?(env) # does this happen during testing?
      _process_unauthenticated(env)
    else
      result
    end
  when Hash
    _process_unauthenticated(env, result)
  else
    result
  end
end
_process_unauthenticated(env, options = {}) click to toggle source
# File lib/devise/test/controller_helpers.rb, line 123
def _process_unauthenticated(env, options = {})
  options[:action] ||= :unauthenticated
  proxy = request.env['warden']
  result = options[:result] || proxy.result

  ret = case result
  when :redirect
    body = proxy.message || "You are being redirected to #{proxy.headers['Location']}"
    [proxy.status, proxy.headers, [body]]
  when :custom
    proxy.custom_response
  else
    request.env["PATH_INFO"] = "/#{options[:action]}"
    request.env["warden.options"] = options
    Warden::Manager._run_callbacks(:before_failure, env, options)

    status, headers, response = Devise.warden_config[:failure_app].call(env).to_a
    @controller.response.headers.merge!(headers)
    @controller.response.content_type = headers["Content-Type"] unless Rails.version.start_with?('5')
    @controller.status = status
    @controller.response.body = response.body
    nil # causes process return @response
  end

  # ensure that the controller response is set up. In production, this is
  # not necessary since warden returns the results to rack. However, at
  # testing time, we want the response to be available to the testing
  # framework to verify what would be returned to rack.
  if ret.is_a?(Array)
    status, headers, body = *ret
    # ensure the controller response is set to our response.
    @controller.response ||= @response
    @response.status = status
    @response.headers.merge!(headers)
    @response.body = body
  end

  ret
end