require 'bourne' module Shoulda # :nodoc: module Matchers module Independent # :nodoc: # Ensure that a given method is delegated properly. # # Basic Syntax: # it { should delegate_method(:deliver_mail).to(:mailman) } # # Options: # * :as - tests that the object being delegated to is called with a certain method (defaults to same name as delegating method) # * :with_arguments - tests that the method on the object being delegated to is called with certain arguments # # Examples: # it { should delegate_method(:deliver_mail).to(:mailman).as(:deliver_with_haste) # it { should delegate_method(:deliver_mail).to(:mailman).with_arguments('221B Baker St.', :hastily => true) # def delegate_method(delegating_method) DelegateMatcher.new(delegating_method) end class DelegateMatcher def initialize(delegating_method) @delegating_method = delegating_method end def matches?(subject) @subject = subject ensure_target_method_is_present! begin extend Mocha::API stubbed_object = stub(method_on_target) subject.stubs(@target_method).returns(stubbed_object) subject.send(@delegating_method) stubbed_object.should have_received(method_on_target).with(*@delegated_arguments) rescue NoMethodError, RSpec::Expectations::ExpectationNotMetError, Mocha::ExpectationError false end end def does_not_match?(subject) raise InvalidDelegateMatcher end def to(target_method) @target_method = target_method self end def as(method_on_target) @method_on_target = method_on_target self end def with_arguments(*arguments) @delegated_arguments = arguments self end def failure_message base = "Expected #{delegating_method_name} to delegate to #{target_method_name}" add_clarifications_to(base) end private def add_clarifications_to(message) if @delegated_arguments.present? message << " with arguments: #{@delegated_arguments.inspect}" end if @method_on_target.present? message << " as :#{@method_on_target}" end message end def delegating_method_name method_name_with_class(@delegating_method) end def target_method_name method_name_with_class(@target_method) end def method_name_with_class(method) if Class === @subject @subject.name + '.' + method.to_s else @subject.class.name + '#' + method.to_s end end def method_on_target @method_on_target || @delegating_method end def ensure_target_method_is_present! if @target_method.blank? raise TargetNotDefinedError end end end class DelegateMatcher::TargetNotDefinedError < StandardError def message 'Delegation needs a target. Use the #to method to define one, e.g. `post_office.should delegate(:deliver_mail).to(:mailman)`' end end class DelegateMatcher::InvalidDelegateMatcher < StandardError def message '#delegate_to does not support #should_not syntax.' end end end end end