RSpec::Support.require_rspec_mocks 'verifying_message_expecation' RSpec::Support.require_rspec_mocks 'method_reference' module RSpec module Mocks # @private class CallbackInvocationStrategy def call(doubled_module) RSpec::Mocks.configuration.verifying_double_callbacks.each do |block| block.call doubled_module end end end # @private class NoCallbackInvocationStrategy def call(_doubled_module) end end # @private module VerifyingProxyMethods def add_stub(method_name, opts={}, &implementation) ensure_implemented(method_name) super end def add_simple_stub(method_name, *args) ensure_implemented(method_name) super end def add_message_expectation(method_name, opts={}, &block) ensure_implemented(method_name) super end def ensure_implemented(method_name) return unless method_reference[method_name].unimplemented? @error_generator.raise_unimplemented_error( @doubled_module, method_name, @object ) end def ensure_publicly_implemented(method_name, _object) ensure_implemented(method_name) visibility = method_reference[method_name].visibility return if visibility == :public @error_generator.raise_non_public_error(method_name, visibility) end end # A verifying proxy mostly acts like a normal proxy, except that it # contains extra logic to try and determine the validity of any expectation # set on it. This includes whether or not methods have been defined and the # validatiy of arguments on method calls. # # In all other ways this behaves like a normal proxy. It only adds the # verification behaviour to specific methods then delegates to the parent # implementation. # # These checks are only activated if the doubled class has already been # loaded, otherwise they are disabled. This allows for testing in # isolation. # # @private class VerifyingProxy < TestDoubleProxy include VerifyingProxyMethods def initialize(object, order_group, doubled_module, method_reference_class) super(object, order_group) @object = object @doubled_module = doubled_module @method_reference_class = method_reference_class # A custom method double is required to pass through a way to lookup # methods to determine their parameters. This is only relevant if the doubled # class is loaded. @method_doubles = Hash.new do |h, k| h[k] = VerifyingMethodDouble.new(@object, k, self, method_reference[k]) end end def method_reference @method_reference ||= Hash.new do |h, k| h[k] = @method_reference_class.for(@doubled_module, k) end end def visibility_for(method_name) method_reference[method_name].visibility end def validate_arguments!(method_name, args) @method_doubles[method_name].validate_arguments!(args) end end # @private DEFAULT_CALLBACK_INVOCATION_STRATEGY = CallbackInvocationStrategy.new # @private class VerifyingPartialDoubleProxy < PartialDoubleProxy include VerifyingProxyMethods def initialize(object, expectation_ordering, optional_callback_invocation_strategy=DEFAULT_CALLBACK_INVOCATION_STRATEGY) super(object, expectation_ordering) @doubled_module = DirectObjectReference.new(object) # A custom method double is required to pass through a way to lookup # methods to determine their parameters. @method_doubles = Hash.new do |h, k| h[k] = VerifyingExistingMethodDouble.for(object, k, self) end optional_callback_invocation_strategy.call(@doubled_module) end def method_reference @method_doubles end end # @private class VerifyingPartialClassDoubleProxy < VerifyingPartialDoubleProxy include PartialClassDoubleProxyMethods end # @private class VerifyingMethodDouble < MethodDouble def initialize(object, method_name, proxy, method_reference) super(object, method_name, proxy) @method_reference = method_reference end def message_expectation_class VerifyingMessageExpectation end def add_expectation(*args, &block) # explict params necessary for 1.8.7 see #626 super(*args, &block).tap { |x| x.method_reference = @method_reference } end def add_stub(*args, &block) # explict params necessary for 1.8.7 see #626 super(*args, &block).tap { |x| x.method_reference = @method_reference } end def proxy_method_invoked(obj, *args, &block) validate_arguments!(args) super end def validate_arguments!(actual_args) @method_reference.with_signature do |signature| verifier = Support::StrictSignatureVerifier.new(signature, actual_args) raise ArgumentError, verifier.error_message unless verifier.valid? end end end # A VerifyingMethodDouble fetches the method to verify against from the # original object, using a MethodReference. This works for pure doubles, # but when the original object is itself the one being modified we need to # collapse the reference and the method double into a single object so that # we can access the original pristine method definition. # # @private class VerifyingExistingMethodDouble < VerifyingMethodDouble def initialize(object, method_name, proxy) super(object, method_name, proxy, self) @valid_method = object.respond_to?(method_name, true) # Trigger an eager find of the original method since if we find it any # later we end up getting a stubbed method with incorrect arity. save_original_implementation_callable! end def with_signature yield Support::MethodSignature.new(original_implementation_callable) end def unimplemented? !@valid_method end def self.for(object, method_name, proxy) if ClassNewMethodReference.applies_to?(method_name) { object } VerifyingExistingClassNewMethodDouble else self end.new(object, method_name, proxy) end end # Used in place of a `VerifyingExistingMethodDouble` for the specific case # of mocking or stubbing a `new` method on a class. In this case, we substitute # the method signature from `#initialize` since new's signature is just `*args`. # # @private class VerifyingExistingClassNewMethodDouble < VerifyingExistingMethodDouble def with_signature yield Support::MethodSignature.new(object.instance_method(:initialize)) end end end end