From e9daa566397c35237805c001bf5deaccab12007a Mon Sep 17 00:00:00 2001 From: Myron Marston Date: Tue, 22 Oct 2013 23:38:41 -0700 Subject: [PATCH 1/2] Refactor: reify the 3 types of object references we have. --- lib/rspec/mocks/example_methods.rb | 19 ++--- lib/rspec/mocks/object_reference.rb | 71 +++++++++++++------ .../verifying_message_expecation_spec.rb | 2 +- 3 files changed, 54 insertions(+), 38 deletions(-) diff --git a/lib/rspec/mocks/example_methods.rb b/lib/rspec/mocks/example_methods.rb index 67f540b27..0f5bf5d2f 100644 --- a/lib/rspec/mocks/example_methods.rb +++ b/lib/rspec/mocks/example_methods.rb @@ -42,7 +42,8 @@ def double(*args) # allowed to be stubbed. In all other ways it behaves like a # [double](double). def instance_double(doubled_class, *args) - declare_instance_or_class_double(InstanceVerifyingDouble, doubled_class, *args) + ref = ObjectReference.for(doubled_class) + declare_verifying_double(InstanceVerifyingDouble, ref, *args) end # @overload class_double(doubled_class) @@ -56,7 +57,8 @@ def instance_double(doubled_class, *args) # allowed to be stubbed. In all other ways it behaves like a # [double](double). def class_double(doubled_class, *args) - declare_instance_or_class_double(ClassVerifyingDouble, doubled_class, *args) + ref = ObjectReference.for(doubled_class) + declare_verifying_double(ClassVerifyingDouble, ref, *args) end # @overload object_double(object_or_name) @@ -70,12 +72,7 @@ def class_double(doubled_class, *args) # is provided, it is assumed to reference a constant object which is used # for verification. In all other ways it behaves like a [double](double). def object_double(object_or_name, *args) - ref = if object_or_name.is_a?(String) - ModuleReference.new(object_or_name) - else - ObjectReference.new(object_or_name) - end - + ref = ObjectReference.for(object_or_name, :allow_direct_object_refs) declare_verifying_double(ObjectVerifyingDouble, ref, *args) end @@ -177,12 +174,6 @@ def self.included(klass) private - def declare_instance_or_class_double(type, constant_or_name, *args) - ref = ModuleReference.new(constant_or_name) - - declare_verifying_double(type, ref, *args) - end - def declare_verifying_double(type, ref, *args) if RSpec::Mocks.configuration.verify_doubled_constant_names? && !ref.defined? diff --git a/lib/rspec/mocks/object_reference.rb b/lib/rspec/mocks/object_reference.rb index 692980f11..f1162f8db 100644 --- a/lib/rspec/mocks/object_reference.rb +++ b/lib/rspec/mocks/object_reference.rb @@ -1,10 +1,29 @@ module RSpec module Mocks - # An abstraction in front of objects so that non-loaded objects can be - # worked with. The null case is for concrete objects that are always - # loaded. See `ModuleReference` for an example of non-loaded objects. + # @api private class ObjectReference + # Returns an appropriate Object or Module reference based + # on the given argument. + def self.for(object_module_or_name, allow_direct_object_refs = false) + case object_module_or_name + when Module then DirectModuleReference.new(object_module_or_name) + when String then NamedObjectReference.new(object_module_or_name) + else + if allow_direct_object_refs + DirectObjectReference.new(object_module_or_name) + else + raise ArgumentError, + "Module or String expected, got #{object_module_or_name.inspect}" + end + end + end + end + + # Used when an object is passed to `object_double`. + # Represents a reference to that object. + # @api private + class DirectObjectReference def initialize(object) @object = object end @@ -27,39 +46,45 @@ def when_loaded end end - # Provides a consistent interface for dealing with modules that may or may - # not be defined. - # - # @private - class ModuleReference - def initialize(module_or_name) - case module_or_name - when Module then @module = module_or_name - when String then @name = module_or_name - else raise ArgumentError, - "Module or String expected, got #{module_or_name.inspect}" - end + # Used when a module is passed to `class_double` or `instance_double`. + # Represents a reference to that module. + # @api private + class DirectModuleReference < DirectObjectReference + def const_to_replace + @object.name + end + alias description const_to_replace + end + + # Used when a string is passed to `class_double`, `instance_double` + # or `object_double`. + # Represents a reference to the object named (via a constant lookup) + # by the string. + # @api private + class NamedObjectReference + def initialize(const_name) + @const_name = const_name end def defined? - !!original_module + !!object end def const_to_replace - @name || @module.name + @const_name end - - alias_method :description, :const_to_replace + alias description const_to_replace def when_loaded(&block) - yield original_module if original_module + yield object if object end - private + private - def original_module - @module ||= Constant.original(@name).original_value + def object + @object ||= Constant.original(@const_name).original_value end end end end + diff --git a/spec/rspec/mocks/verifying_message_expecation_spec.rb b/spec/rspec/mocks/verifying_message_expecation_spec.rb index 191b742e3..7321b1f32 100644 --- a/spec/rspec/mocks/verifying_message_expecation_spec.rb +++ b/spec/rspec/mocks/verifying_message_expecation_spec.rb @@ -5,7 +5,7 @@ module Mocks describe VerifyingMessageExpectation do describe '#with' do let(:error_generator) { double.as_null_object } - let(:string_module_reference) { ModuleReference.new(String) } + let(:string_module_reference) { DirectModuleReference.new(String) } subject { null = double.as_null_object From 477cd1f33eef6d853b4560e4a6751cef5c511215 Mon Sep 17 00:00:00 2001 From: Myron Marston Date: Tue, 22 Oct 2013 23:40:59 -0700 Subject: [PATCH 2/2] Clarify docs. It's confusing to talk about "instance methods" for `object_double` because it deals with `object.methods`, not `object.instance_methods`. --- lib/rspec/mocks/example_methods.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rspec/mocks/example_methods.rb b/lib/rspec/mocks/example_methods.rb index 0f5bf5d2f..39faf2394 100644 --- a/lib/rspec/mocks/example_methods.rb +++ b/lib/rspec/mocks/example_methods.rb @@ -67,8 +67,8 @@ def class_double(doubled_class, *args) # @param stubs [Hash] (optional) hash of message/return-value pairs # @return ObjectVerifyingDouble # - # Constructs a test double against a specific object. Only instance - # methods on the object are allowed to be stubbed. If a String argument + # Constructs a test double against a specific object. Only the methods + # the object responds to are allowed to be stubbed. If a String argument # is provided, it is assumed to reference a constant object which is used # for verification. In all other ways it behaves like a [double](double). def object_double(object_or_name, *args)