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