class SimpleStub::ForInstanceMethod

Stub for instance methods, without any alias_method. This class internally prepends a module into the target class. {#apply} adds a method into the prepended module. {#reset} removes a method from the prepended module.

This class must be stateless for the usecase that the caller of {#apply} and the caller of {#reset} can be different. Both

@stub_user_name = SimpleStub::ForInstanceMethod(User, :name) { 'YusukeIwaki' }
@stub_user_name.apply!
# Any username is YusukeIwaki here
@stub_user_name.reset!

and

stub_user_name = SimpleStub::ForInstanceMethod(User, :name) { 'YusukeIwaki' }
stub_user_name.apply!
# Any username is YusukeIwaki here
stub_user_name = SimpleStub::ForInstanceMethod(User, :name) { 'YusukeIwaki' }
stub_user_name.reset!

should work.

Public Class Methods

new(klass, method_name, &impl) click to toggle source

@param klass [Class] @param method_name [Symbol]

# File lib/simple_stub/for_instance_method.rb, line 33
def initialize(klass, method_name, &impl)
  raise ArgumentError, "klass must be a Class. #{klass.class} specified." unless klass.is_a?(Class)
  raise ArgumentError, 'method name must be a Symbol.' unless method_name.is_a?(Symbol)

  @klass = klass
  @method_name = method_name
  @impl = impl
end

Public Instance Methods

apply() click to toggle source

Safer version of {#apply!} Nothing happens when the stub is already applied.

# File lib/simple_stub/for_instance_method.rb, line 44
def apply
  return if stub_defined?

  apply_stub
end
apply!() click to toggle source

Apply the stub. If the stub is already applied, raises error.

# File lib/simple_stub/for_instance_method.rb, line 51
def apply!
  raise AlreadyAppliedError, "The stub for #{@klass}##{@method_name} is already applied" if stub_defined?

  apply_stub
end
reset() click to toggle source

Safer version of {#reset!}. Nothing happens when the stub is already applied.

# File lib/simple_stub/for_instance_method.rb, line 59
def reset
  return unless stub_defined?

  reset_stub
end
reset!() click to toggle source

Remove the stub and revert to the original implementation. If the stub is not applied or already reset, raise error.

# File lib/simple_stub/for_instance_method.rb, line 66
def reset!
  raise NotAppliedError, "The stub for #{@klass}##{@method_name} is already applied" unless stub_defined?

  reset_stub
end

Private Instance Methods

apply_stub() click to toggle source
# File lib/simple_stub/for_instance_method.rb, line 74
def apply_stub
  raise ArgumentError, 'Block must be given for applying stub' unless @impl

  # define_method is private on Ruby <= 2.4
  impl_module.send(:define_method, @method_name, &@impl)
end
create_and_apply_impl_module() click to toggle source
# File lib/simple_stub/for_instance_method.rb, line 94
def create_and_apply_impl_module
  Module.new.tap do |mod|
    # Name the module here
    ForInstanceMethod.const_set(impl_module_name, mod)

    @klass.prepend(mod)
  end
end
impl_module() click to toggle source
# File lib/simple_stub/for_instance_method.rb, line 86
def impl_module
  impl_module_or_nil || create_and_apply_impl_module
end
impl_module_name() click to toggle source
# File lib/simple_stub/for_instance_method.rb, line 107
def impl_module_name
  @impl_module_name ||= "StubImpl#{klass_digest}"
end
impl_module_or_nil() click to toggle source
# File lib/simple_stub/for_instance_method.rb, line 90
def impl_module_or_nil
  ForInstanceMethod.const_get(impl_module_name) if ForInstanceMethod.const_defined?(impl_module_name)
end
klass_digest() click to toggle source
# File lib/simple_stub/for_instance_method.rb, line 103
def klass_digest
  @klass_digest ||= Digest::SHA256.hexdigest(@klass.to_s)
end
reset_stub() click to toggle source
# File lib/simple_stub/for_instance_method.rb, line 81
def reset_stub
  # remove_method is private on Ruby <= 2.4
  impl_module.send(:remove_method, @method_name)
end
stub_defined?() click to toggle source
# File lib/simple_stub/for_instance_method.rb, line 111
def stub_defined?
  # Avoid creating module just in asking if stub exists.
  impl_module_or_nil&.method_defined?(@method_name)
end