lib/rspec/mocks/method_double.rb in rspec-mocks-2.99.4 vs lib/rspec/mocks/method_double.rb in rspec-mocks-3.0.0.beta1
- old
+ new
@@ -1,33 +1,39 @@
module RSpec
module Mocks
# @private
- class MethodDouble < Hash
+ class MethodDouble
# @private
- attr_reader :method_name, :object
+ attr_reader :method_name, :object, :expectations, :stubs
# @private
def initialize(object, method_name, proxy)
@method_name = method_name
@object = object
@proxy = proxy
- @method_stasher = InstanceMethodStasher.new(object_singleton_class, @method_name)
+ @original_visibility = nil
+ @method_stasher = InstanceMethodStasher.new(object, method_name)
@method_is_proxied = false
- store(:expectations, [])
- store(:stubs, [])
+ @expectations = []
+ @stubs = []
end
- # @private
- def expectations
- self[:expectations]
+ def original_method
+ # If original method is not present, uses the `method_missing`
+ # handler of the object. This accounts for cases where the user has not
+ # correctly defined `respond_to?`, and also 1.8 which does not provide
+ # method handles for missing methods even if `respond_to?` is correct.
+ @original_method ||=
+ @method_stasher.original_method ||
+ @proxy.method_handle_for(method_name) ||
+ Proc.new do |*args, &block|
+ @object.__send__(:method_missing, @method_name, *args, &block)
+ end
end
- # @private
- def stubs
- self[:stubs]
- end
+ alias_method :save_original_method!, :original_method
# @private
def visibility
if TestDouble === @object
'public'
@@ -38,168 +44,66 @@
else
'public'
end
end
- class ProcWithBlock < Struct.new(:object, :method_name)
-
- def call(*args, &block)
- self.object.__send__(:method_missing, self.method_name, *args, &block)
- end
-
- end
-
# @private
- def original_method
- if @method_stasher.method_is_stashed?
- # Example: a singleton method defined on @object
- ::RSpec::Mocks.method_handle_for(@object, @method_stasher.stashed_method_name)
- elsif meth = original_unrecorded_any_instance_method
- # Example: a method that has been mocked through
- # klass.any_instance.should_receive(:msg).and_call_original
- # any_instance.should_receive(:msg) causes the method to be
- # replaced with a proxy method, and then `and_call_original`
- # is recorded and played back on the object instance. We need
- # special handling here to get a handle on the original method
- # object rather than the proxy method.
- meth
- else
- # Example: an instance method defined on one of @object's ancestors.
- original_method_from_ancestry
- 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.
- ProcWithBlock.new(@object,@method_name)
- end
-
- def original_unrecorded_any_instance_method
- return nil unless any_instance_class_recorder_observing_method?(@object.class)
- alias_name = ::RSpec::Mocks.any_instance_recorder_for(@object.class).build_alias_method_name(@method_name)
- @object.method(alias_name)
- end
-
- def any_instance_class_recorder_observing_method?(klass)
- return true if ::RSpec::Mocks.any_instance_recorder_for(klass).already_observing?(@method_name)
- superklass = klass.superclass
- return false if superklass.nil?
- any_instance_class_recorder_observing_method?(superklass)
- end
-
- our_singleton_class = class << self; self; end
- if our_singleton_class.ancestors.include? our_singleton_class
- # In Ruby 2.1, ancestors include the correct ancestors, including the singleton classes
- def original_method_from_ancestry
- # Lookup in the ancestry, skipping over the singleton class itself
- original_method_from_ancestor(object_singleton_class.ancestors.drop(1))
- end
- else
- # @private
- def original_method_from_ancestry
- original_method_from_ancestor(object_singleton_class.ancestors)
- 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
-
- def original_method_from_ancestor(ancestors)
- klass, *rest = ancestors
- klass.instance_method(@method_name).bind(@object)
- rescue NameError
- raise if rest.empty?
- original_method_from_ancestor(rest)
- 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: #{CallerFilter.first_non_rspec_line}
- WARNING
-
- @object.superclass.method(@method_name)
- end
- end
- # @private
def object_singleton_class
class << @object; self; end
end
# @private
def configure_method
- @original_visibility = visibility_for_method
+ @original_visibility = [visibility, method_name]
@method_stasher.stash unless @method_is_proxied
define_proxy_method
end
# @private
def define_proxy_method
return if @method_is_proxied
- object_singleton_class.class_eval <<-EOF, __FILE__, __LINE__ + 1
- def #{@method_name}(*args, &block)
- ::RSpec::Mocks.proxy_for(self).message_received :#{@method_name}, *args, &block
+ save_original_method!
+
+ object_singleton_class.class_exec(self, method_name, visibility) do |method_double, method_name, visibility|
+ define_method(method_name) do |*args, &block|
+ method_double.proxy_method_invoked(self, *args, &block)
end
- #{visibility_for_method}
- EOF
+ self.__send__ visibility, method_name
+ end
+
@method_is_proxied = true
end
+ # The implementation of the proxied method. Subclasses may override this
+ # method to perform additional operations.
+ #
# @private
- def visibility_for_method
- "#{visibility} :#{method_name}"
+ def proxy_method_invoked(obj, *args, &block)
+ @proxy.message_received method_name, *args, &block
end
# @private
def restore_original_method
return unless @method_is_proxied
object_singleton_class.__send__(:remove_method, @method_name)
- @method_stasher.restore
+ if @method_stasher.method_is_stashed?
+ @method_stasher.restore
+ end
restore_original_visibility
@method_is_proxied = false
end
# @private
def restore_original_visibility
- return unless object_singleton_class.method_defined?(@method_name) || object_singleton_class.private_method_defined?(@method_name)
- object_singleton_class.class_eval(@original_visibility, __FILE__, __LINE__ + 1)
+ return unless @original_visibility &&
+ (object_singleton_class.method_defined?(@method_name) ||
+ object_singleton_class.private_method_defined?(@method_name))
+
+ object_singleton_class.__send__(*@original_visibility)
end
# @private
def verify
expectations.each {|e| e.verify_messages_received}
@@ -215,31 +119,65 @@
def clear
expectations.clear
stubs.clear
end
+ # The type of message expectation to create has been extracted to its own
+ # method so that subclasses can override it.
+ #
# @private
+ def message_expectation_class
+ MessageExpectation
+ end
+
+ # @private
def add_expectation(error_generator, expectation_ordering, expected_from, opts, &implementation)
configure_method
- expectation = MessageExpectation.new(error_generator, expectation_ordering,
+ expectation = message_expectation_class.new(error_generator, expectation_ordering,
expected_from, self, 1, opts, &implementation)
expectations << expectation
expectation
end
# @private
def build_expectation(error_generator, expectation_ordering)
expected_from = IGNORED_BACKTRACE_LINE
- MessageExpectation.new(error_generator, expectation_ordering, expected_from, self)
+ message_expectation_class.new(error_generator, expectation_ordering, expected_from, self)
end
# @private
def add_stub(error_generator, expectation_ordering, expected_from, opts={}, &implementation)
configure_method
- stub = MessageExpectation.new(error_generator, expectation_ordering, expected_from,
+ stub = message_expectation_class.new(error_generator, expectation_ordering, expected_from,
self, :any, opts, &implementation)
stubs.unshift stub
stub
+ end
+
+ # A simple stub can only return a concrete value for a message, and
+ # cannot match on arguments. It is used as an optimization over
+ # `add_stub` / `add_expectation` where it is known in advance that this
+ # is all that will be required of a stub, such as when passing attributes
+ # to the `double` example method. They do not stash or restore existing method
+ # definitions.
+ #
+ # @private
+ def add_simple_stub(method_name, response)
+ setup_simple_method_double method_name, response, stubs
+ end
+
+ # @private
+ def add_simple_expectation(method_name, response, error_generator, backtrace_line)
+ setup_simple_method_double method_name, response, expectations, error_generator, backtrace_line
+ end
+
+ # @private
+ def setup_simple_method_double(method_name, response, collection, error_generator = nil, backtrace_line = nil)
+ define_proxy_method
+
+ me = SimpleMessageExpectation.new(method_name, response, error_generator, backtrace_line)
+ collection.unshift me
+ me
end
# @private
def add_default_stub(*args, &implementation)
return if stubs.any?