lib/spy/subroutine.rb in spy-1.0.0 vs lib/spy/subroutine.rb in spy-1.0.1

- old
+ new

@@ -50,13 +50,26 @@ if original_method_visibility || !hook_opts[:force] @original_method = current_method end - define_method_with = singleton_method ? :define_singleton_method : :define_method - base_object.send(define_method_with, method_name, override_method) + if original_method && original_method.owner == base_object + original_method.owner.send(:remove_method, method_name) + end + if singleton_method + if base_object.singleton_class.method_defined?(method_name) || base_object.singleton_class.private_method_defined?(method_name) + base_object.singleton_class.alias_method(method_name, method_name) + end + base_object.define_singleton_method(method_name, override_method) + else + if base_object.method_defined?(method_name) || base_object.private_method_defined?(method_name) + base_object.alias_method(method_name, method_name) + end + base_object.define_method(method_name, override_method) + end + if [:public, :protected, :private].include? hook_opts[:visibility] method_owner.send(hook_opts[:visibility], method_name) end Agency.instance.recruit(self) @@ -67,15 +80,14 @@ # unhooks method from object # @return [self] def unhook raise NeverHookedError, "'#{method_name}' method has not been hooked" unless hooked? + method_owner.send(:remove_method, method_name) if original_method && method_owner == original_method.owner - method_owner.send(:define_method, method_name, original_method) - method_owner.send(original_method_visibility, method_name) if original_method_visibility - else - method_owner.send(:remove_method, method_name) + original_method.owner.send(:define_method, method_name, original_method) + original_method.owner.send(original_method_visibility, method_name) if original_method_visibility end clear_method! Agency.instance.retire(self) self @@ -103,21 +115,21 @@ # spy.and_return(true) # spy.and_return { true } # spy.and_return(force: true) { |invalid_arity| true } # # @return [self] - def and_return(value = nil) + def and_return(value = nil, &block) @do_not_check_plan_arity = false if block_given? if value.is_a?(Hash) && value.has_key?(:force) @do_not_check_plan_arity = !!value[:force] elsif !value.nil? raise ArgumentError, "value and block conflict. Choose one" end - @plan = Proc.new + @plan = block check_for_too_many_arguments!(@plan) else @plan = Proc.new { value } end self @@ -134,17 +146,33 @@ end # tells the spy to call the original method # @return [self] def and_call_through - @plan = Proc.new do |*args, &block| - if original_method - original_method.call(*args, &block) - else - base_object.send(:method_missing, method_name, *args, &block) + if @base_object.is_a? Class + @plan = Proc.new do |object, *args, &block| + if original_method + if original_method.is_a? UnboundMethod + bound_method = original_method.bind(object) + bound_method.call(*args, &block) + else + original_method.call(*args, &block) + end + else + base_object.send(:method_missing, method_name, *args, &block) + end end + else + @plan = Proc.new do |*args, &block| + if original_method + original_method.call(*args, &block) + else + base_object.send(:method_missing, method_name, *args, &block) + end + end end + self end # @overload and_raise # @overload and_raise(ExceptionClass) @@ -191,24 +219,32 @@ end # check if the method was called with the exact arguments # @param args Arguments that should have been sent to the method # @return [Boolean] - def has_been_called_with?(*args) + def has_been_called_with?(*args, &block) raise NeverHookedError unless @was_hooked - match = block_given? ? Proc.new : proc { |call| call.args == args } + match = block_given? ? block : proc { |call| call.args == args } calls.any?(&match) end # invoke that the method has been called. You really shouldn't use this # method. def invoke(object, args, block, called_from) check_arity!(args.size) - result = if @plan - check_for_too_many_arguments!(@plan) - @plan.call(*args, &block) - end + + if base_object.is_a? Class + result = if @plan + check_for_too_many_arguments!(@plan) + @plan.call(object, *args, &block) + end + else + result = if @plan + check_for_too_many_arguments!(@plan) + @plan.call(*args, &block) + end + end ensure calls << CallLog.new(object, called_from, args, block, result) end # reset the call log @@ -271,9 +307,11 @@ def check_for_too_many_arguments!(block) return if @do_not_check_plan_arity || arity_range.nil? min_arity = block.arity min_arity = min_arity.abs - 1 if min_arity < 0 + + min_arity -=1 if base_object.is_a? Class # Instance-method procs take an extra param for receiving object if min_arity > arity_range.max raise ArgumentError.new("block requires #{min_arity} arguments while original_method require a maximum of #{arity_range.max}") end end