lib/rspec/mocks/method_double.rb in rspec-mocks-2.11.3 vs lib/rspec/mocks/method_double.rb in rspec-mocks-2.12.0

- old
+ new

@@ -1,19 +1,19 @@ module RSpec module Mocks # @private class MethodDouble < Hash # @private - attr_reader :method_name + attr_reader :method_name, :object # @private def initialize(object, method_name, proxy) @method_name = method_name @object = object @proxy = proxy - @stashed_method = StashedInstanceMethod.new(object_singleton_class, @method_name) + @method_stasher = InstanceMethodStasher.new(object_singleton_class, @method_name) @method_is_proxied = false store(:expectations, []) store(:stubs, []) end @@ -39,19 +39,93 @@ 'public' end end # @private + def original_method + if @method_stasher.method_is_stashed? + # Example: a singleton method defined on @object + method_handle_for(@object, @method_stasher.stashed_method_name) + else + begin + # Example: an instance method defined on @object's class. + @object.class.instance_method(@method_name).bind(@object) + rescue NameError + raise unless @object.respond_to?(:superclass) + + # Example: a singleton method defined on @object's superclass. + # + # Note: we have to give precedence to instance methods + # defined on @object's class, because in a case like: + # + # `klass.should_receive(:new).and_call_original` + # + # ...we want `Class#new` bound to `klass` (which will return + # an instance of `klass`), not `klass.superclass.new` (which + # would return an instance of `klass.superclass`). + original_method_from_superclass + end + end + rescue NameError + # We have no way of knowing if the object's method_missing + # will handle this message or not...but we can at least try. + # If it's not handled, a `NoMethodError` will be raised, just + # like normally. + Proc.new do |*args, &block| + @object.__send__(:method_missing, @method_name, *args, &block) + end + end + + if RUBY_VERSION.to_f > 1.8 + # @private + def original_method_from_superclass + @object.superclass. + singleton_class. + instance_method(@method_name). + bind(@object) + end + else + # Our implementation for 1.9 (above) causes an error on 1.8: + # TypeError: singleton method bound for a different object + # + # This doesn't work quite right in all circumstances but it's the + # best we can do. + # @private + def original_method_from_superclass + ::Kernel.warn <<-WARNING.gsub(/^ +\|/, '') + | + |WARNING: On ruby 1.8, rspec-mocks is unable to bind the original + |`#{@method_name}` method to your partial mock object (#{@object}) + |for `and_call_original`. The superclass's `#{@method_name}` is being + |used instead; however, it may not work correctly when executed due + |to the fact that `self` will be #{@object.superclass}, not #{@object}. + | + |Called from: #{caller[2]} + WARNING + + @object.superclass.method(@method_name) + end + end + + # @private + OBJECT_METHOD_METHOD = ::Object.instance_method(:method) + + # @private + def method_handle_for(object, method_name) + OBJECT_METHOD_METHOD.bind(object).call(method_name) + end + + # @private def object_singleton_class class << @object; self; end end # @private def configure_method RSpec::Mocks::space.add(@object) if RSpec::Mocks::space warn_if_nil_class - @stashed_method.stash unless @method_is_proxied + @method_stasher.stash unless @method_is_proxied define_proxy_method end # @private def define_proxy_method @@ -74,11 +148,11 @@ # @private def restore_original_method return unless @method_is_proxied object_singleton_class.__send__(:remove_method, @method_name) - @stashed_method.restore + @method_stasher.restore @method_is_proxied = false end # @private def verify @@ -102,27 +176,30 @@ def add_expectation(error_generator, expectation_ordering, expected_from, opts, &implementation) configure_method expectation = if existing_stub = stubs.first existing_stub.build_child(expected_from, 1, opts, &implementation) else - MessageExpectation.new(error_generator, expectation_ordering, expected_from, @method_name, 1, opts, &implementation) + MessageExpectation.new(error_generator, expectation_ordering, + expected_from, self, 1, opts, &implementation) end expectations << expectation expectation end # @private def add_negative_expectation(error_generator, expectation_ordering, expected_from, &implementation) configure_method - expectation = NegativeMessageExpectation.new(error_generator, expectation_ordering, expected_from, @method_name, &implementation) + expectation = NegativeMessageExpectation.new(error_generator, expectation_ordering, + expected_from, self, &implementation) expectations.unshift expectation expectation end # @private def add_stub(error_generator, expectation_ordering, expected_from, opts={}, &implementation) configure_method - stub = MessageExpectation.new(error_generator, expectation_ordering, expected_from, @method_name, :any, opts, &implementation) + stub = MessageExpectation.new(error_generator, expectation_ordering, expected_from, + self, :any, opts, &implementation) stubs.unshift stub stub end # @private