Last Updated: February 25, 2016
·
2.471K
· vlado

Stub devise authentication in controller specs with multiple scopes

I wanted to fake devise authentication in my controller specs.

Solution proposed in devise wiki (https://github.com/plataformatec/devise/wiki/How-To:-Stub-authentication-in-controller-specs) did not work cause it stubs warden authenticate! in a way that does not check for scope and works properly only in case you have only one scope.

def sign_in(user = double('user'))
  if user.nil?
    allow(request.env['warden']).to receive(:authenticate!).and_throw(:warden, {:scope => :user})
    allow(controller).to receive(:current_user).and_return(nil)
  else
    # no matter what you pass to authenticate!
    # it will always return resource you want (will not check scope)
    allow(request.env['warden']).to receive(:authenticate!).and_return(user)
    allow(controller).to receive(:current_user).and_return(user)
  end
end

If you have multiple scopes you should do something like this:

def fake_sign_in(resource_or_scope = :user, options = {})
  if resource_or_scope.is_a?(Symbol)
    scope = resource_or_scope
    resource = double(resource_or_scope)
  else
    resource = resource_or_scope
    scope = options[:scope] || resource.class.to_s.underscore
  end
  # Since we have multiple scopes we need to check if scope option provided to
  # authenticate! matches scope that we are logging in
  allow(request.env['warden']).to receive(:authenticate!) do |options|
    if options[:scope].to_sym == scope.to_sym
      resource
    else
      throw :warden, :scope => scope
    end
  end
  allow(controller).to receive("current_#{scope}").and_return(resource)
end

def fake_sign_out(scope = :user)
  allow(request.env['warden']).to receive(:authenticate!).and_throw(:warden, {:scope => scope})
  allow(controller).to receive("current_#{scope}").and_return(nil)
end

Note that I also separated sign in and sign out.

For full code see https://gist.github.com/vlado/812948efe4fb0667a964