Skip to content

Commit

Permalink
Merge pull request #441 from rspec/refactor-references
Browse files Browse the repository at this point in the history
Refactor references
  • Loading branch information
xaviershay committed Oct 23, 2013
2 parents 79659b5 + 477cd1f commit c429c8b
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 40 deletions.
23 changes: 7 additions & 16 deletions lib/rspec/mocks/example_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -65,17 +67,12 @@ 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)
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

Expand Down Expand Up @@ -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?
Expand Down
71 changes: 48 additions & 23 deletions lib/rspec/mocks/object_reference.rb
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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

2 changes: 1 addition & 1 deletion spec/rspec/mocks/verifying_message_expecation_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit c429c8b

Please sign in to comment.