Custom RSpec matcher: require_authentication_for
Like everyone, I have a bunch of controller actions that need to be protected from unauthorized users. I've got RSpec tests around these methods that look like this:
describe UsersController, 'as guest' do
before( :each ) do
controller.stubs( :current_user ).returns( nil )
end
describe 'index' do
it 'should prevent access' do
lambda { get :index }.should
raise_error( Exceptions::PermissionDenied )
end
end
describe 'edit' do
it 'should prevent access' do
lambda { get :edit, :id => 1 }.should
raise_error( Exceptions::PermissionDenied )
end
end
describe 'create' do
it 'should prevent access' do
lambda { post :create, :id => 1 }.should
raise_error( Exceptions::PermissionDenied )
end
end
end
Does exactly what I want it to do. If I ever screw up and remove index
from
the before_filter :authenticate
, I'll know it when I re-run the tests. But
I've got lots and lots of methods like this, and wanted a more concise way to
specify this behavior.
I did some searching and found quite a few different approaches (most notably this from Ryan Bates, and this plugin), but none of them, I felt, were enough. And so, because I'm a stupid fuck who writes too much code, I threw together this custom RSpec matcher.
# spec/support/matchers/require_authentication_for.rb
module CustomControllerMatchers
def require_authentication_for( action, *args )
options = args.extract_options!
method = args.first.is_a?( Symbol ) ? args.first : :get
RequireAuthenticationMatcher.new( method, action, options, self )
end
class RequireAuthenticationMatcher
def initialize( method, action, args, context )
@method = method
@action = action
@args = args
@context = context
@actual_exception = nil
end
def matches?( controller )
@raised_permission_denied = false
begin
@context.send( @method, @action, @args )
rescue Exceptions::PermissionDenied => @actual_exception
@raised_permission_denied = true
rescue Exception => @actual_exception
foo = 'bar'
end
@raised_permission_denied
end
def failure_message_for_should
if @actual_exception.nil?
"Expected PermissionDenied, but nothing was raised"
else
"Expected PermissionDenied, got #{@actual_exception.inspect}"
end
end
def failure_message_for_should_not
if @actual_exception.nil?
'Not sure what happened'
else
"Expected the call to succeed, but #{@actual_exception.message} was raised"
end
end
def description
"require authentication for #{@action}"
end
end
end
My controller specs can now look like this:
describe UsersController, 'as guest' do
before( :each ) do
controller.stubs( :current_user ).returns( nil )
end
it { should require_authentication_for( :index ) }
it { should require_authentication_for( :edit, :id => 1 ) }
it { should require_authentication_for( :update, :post, :id => 1 ) }
end
It accomplishes the same thing, I know, but to me this is just more readable.