lib/muack/mock.rb in muack-1.5.1 vs lib/muack/mock.rb in muack-1.6.0

- old
+ new

@@ -110,59 +110,105 @@ attr_accessor :__mock_defis, :__mock_disps, :__mock_injected private def __mock_inject_method defi __mock_injected[defi.msg] = defi - # a) ancestors.first is the first module in the method chain. - # it's just the singleton_class when nothing was prepended, - # otherwise the last prepended module. - # b) would be the class in AnyInstanceOf. - target = object.singleton_class.ancestors.first + target = Mock.prepare_target(object.singleton_class, defi.msg) + defi.target = target Mock.store_original_method(target, defi) __mock_inject_mock_method(target, defi) end def __mock_reset_method defi - object.singleton_class.ancestors.first.module_eval do + defi.target.module_eval do remove_method(defi.msg) # restore original method - if public_instance_methods(false).include?(defi.original_method) || - protected_instance_methods(false).include?(defi.original_method) || - private_instance_methods(false).include?(defi.original_method) + if defi.original_method && + Mock.direct_method_defined?(self, defi.original_method) alias_method(defi.msg, defi.original_method) __send__(defi.visibility, defi.msg) remove_method(defi.original_method) end end end - def self.store_original_method klass, defi - visibility = if klass.public_instance_methods(false).include?(defi.msg) - :public - elsif klass.protected_instance_methods(false).include?(defi.msg) - :protected - elsif klass.private_instance_methods(false).include?(defi.msg) - :private + if ::Class.instance_method(:method_defined?).arity == 1 # Ruby 2.5- + def self.direct_method_defined? mod, msg + mod.public_instance_methods(false).include?(msg) || + mod.protected_instance_methods(false).include?(msg) || + mod.private_instance_methods(false).include?(msg) end - if visibility # store original method - original_method = find_new_name(klass, defi.msg) - klass.__send__(:alias_method, original_method, defi.msg) + def self.method_visibility mod, msg + if mod.public_instance_methods(false).include?(msg) + :public + elsif mod.protected_instance_methods(false).include?(msg) + :protected + elsif mod.private_instance_methods(false).include?(msg) + :private + end + end + else # Ruby 2.6+ + def self.direct_method_defined? mod, msg + mod.method_defined?(msg, false) || # this doesn't cover private method + mod.private_method_defined?(msg, false) + end + + def self.method_visibility mod, msg + if mod.public_method_defined?(msg, false) + :public + elsif mod.protected_method_defined?(msg, false) + :protected + elsif mod.private_method_defined?(msg, false) + :private + end + end + end + + def self.prepare_target singleton_class, msg + if singleton_class == singleton_class.ancestors.first # no prepended mod + singleton_class + else # check if we need to prepend an internal module to override + index = singleton_class.ancestors.index(singleton_class) + prepended_modules = singleton_class.ancestors[0...index] + + if prepended_modules.find{ |m| direct_method_defined?(m, msg) } + # prepend an internal module to override the prepended module(s) + name = :MuackPrepended + if singleton_class.const_defined?(name) + singleton_class.const_get(name) + else + prepended = ::Module.new + singleton_class.const_set(name, prepended) + singleton_class.private_constant(name) + singleton_class.prepend(prepended) + prepended + end + else # we don't need to override so singleton class is enough + singleton_class + end + end + end + + def self.store_original_method mod, defi + if visibility = method_visibility(mod, defi.msg) + original_method = find_new_name(mod, defi.msg) + mod.__send__(:alias_method, original_method, defi.msg) defi.original_method = original_method defi.visibility = visibility else defi.visibility = :public end end - def self.find_new_name klass, message, level=0 + def self.find_new_name mod, message, level=0 if level >= (::ENV['MUACK_RECURSION_LEVEL'] || 9).to_i raise CannotFindInjectionName.new(level+1, message) end new_name = "__muack_#{name}_#{level}_#{message}".to_sym - if klass.instance_methods(false).include?(new_name) - find_new_name(klass, message, level+1) + if direct_method_defined?(mod, new_name) + find_new_name(mod, message, level+1) else new_name end end