Last Updated: February 25, 2016
·
2.578K
· 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