class RSpec::Mocks::MethodDouble

@private

Constants

RSpecPrependedModule

We subclass ‘Module` in order to be able to easily detect our prepended module.

Attributes

@private

@private

Public Class Methods

@private

# File rspec-mocks/lib/rspec/mocks/method_double.rb, line 9
def initialize(object, method_name, proxy)
  @method_name = method_name
  @object = object
  @proxy = proxy

  @original_visibility = nil
  @method_stasher = InstanceMethodStasher.new(object, method_name)
  @method_is_proxied = false
  @expectations = []
  @stubs = []
end

Public Instance Methods

@private

# File rspec-mocks/lib/rspec/mocks/method_double.rb, line 213
def add_default_stub(*args, &implementation)
  return if stubs.any?
  add_stub(*args, &implementation)
end

@private

# File rspec-mocks/lib/rspec/mocks/method_double.rb, line 163
def add_expectation(error_generator, expectation_ordering, expected_from, opts, &implementation)
  configure_method
  expectation = message_expectation_class.new(error_generator, expectation_ordering,
                                              expected_from, self, :expectation, opts, &implementation)
  expectations << expectation
  expectation
end

@private

# File rspec-mocks/lib/rspec/mocks/method_double.rb, line 199
def add_simple_expectation(method_name, response, error_generator, backtrace_line)
  setup_simple_method_double method_name, response, expectations, error_generator, backtrace_line
end

A simple stub can only return a concrete value for a message, and cannot match on arguments. It is used as an optimization over ‘add_stub` / `add_expectation` where it is known in advance that this is all that will be required of a stub, such as when passing attributes to the `double` example method. They do not stash or restore existing method definitions.

@private

# File rspec-mocks/lib/rspec/mocks/method_double.rb, line 194
def add_simple_stub(method_name, response)
  setup_simple_method_double method_name, response, stubs
end

@private

# File rspec-mocks/lib/rspec/mocks/method_double.rb, line 178
def add_stub(error_generator, expectation_ordering, expected_from, opts={}, &implementation)
  configure_method
  stub = message_expectation_class.new(error_generator, expectation_ordering, expected_from,
                                       self, :stub, opts, &implementation)
  stubs.unshift stub
  stub
end

@private

# File rspec-mocks/lib/rspec/mocks/method_double.rb, line 172
def build_expectation(error_generator, expectation_ordering)
  expected_from = IGNORED_BACKTRACE_LINE
  message_expectation_class.new(error_generator, expectation_ordering, expected_from, self)
end

@private

# File rspec-mocks/lib/rspec/mocks/method_double.rb, line 149
def clear
  expectations.clear
  stubs.clear
end

@private

# File rspec-mocks/lib/rspec/mocks/method_double.rb, line 59
def configure_method
  @original_visibility = visibility
  @method_stasher.stash unless @method_is_proxied
  define_proxy_method
end

@private

Calls superclass method
# File rspec-mocks/lib/rspec/mocks/method_double.rb, line 66
def define_proxy_method
  return if @method_is_proxied

  save_original_implementation_callable!
  definition_target.class_exec(self, method_name, @original_visibility || visibility) do |method_double, method_name, visibility|
    if RUBY_VERSION.to_f > 3 && method_name == :respond_to?
      define_method(method_name) do |*args, &block|
        # This is a work around for a respond_to? check within kernel_inspect
        if caller_locations[0].label == "Kernel#inspect"
          super(*args, &block)
        else
          method_double.proxy_method_invoked(self, *args, &block)
        end
      end
    else
      define_method(method_name) do |*args, &block|
        method_double.proxy_method_invoked(self, *args, &block)
      end
    end
    # This can't be `if respond_to?(:ruby2_keywords, true)`,
    # see https://github.com/rspec/rspec-mocks/pull/1385#issuecomment-755340298
    ruby2_keywords(method_name) if Module.private_method_defined?(:ruby2_keywords)
    __send__(visibility, method_name)
  end

  @method_is_proxied = true
rescue FrozenError
  raise ArgumentError, "Cannot proxy frozen objects, rspec-mocks relies on proxies for method stubbing and expectations."
end

The type of message expectation to create has been extracted to its own method so that subclasses can override it.

@private

# File rspec-mocks/lib/rspec/mocks/method_double.rb, line 158
def message_expectation_class
  MessageExpectation
end

@private

# File rspec-mocks/lib/rspec/mocks/method_double.rb, line 37
def method_missing_block
  block = Proc.new do |*args, &b|
    @object.__send__(:method_missing, @method_name, *args, &b)
  end
  block.ruby2_keywords if block.respond_to?(:ruby2_keywords)

  block
end

@private

# File rspec-mocks/lib/rspec/mocks/method_double.rb, line 52
def object_singleton_class
  # We can't use @object.singleton_class because this method
  # creates a new singleton class if the object doesn't have one.
  class << @object; self; end
end
# File rspec-mocks/lib/rspec/mocks/method_double.rb, line 21
def original_implementation_callable
  # If original method is not present, uses the `method_missing`
  # handler of the object. This accounts for cases where the user has not
  # correctly defined `respond_to?`.
  @original_implementation_callable ||= original_method || method_missing_block
end
# File rspec-mocks/lib/rspec/mocks/method_double.rb, line 30
def original_method
  @original_method ||=
    @method_stasher.original_method ||
    @proxy.original_method_handle_for(method_name)
end

The implementation of the proxied method. Subclasses may override this method to perform additional operations.

@private

# File rspec-mocks/lib/rspec/mocks/method_double.rb, line 100
def proxy_method_invoked(_obj, *args, &block)
  @proxy.message_received method_name, *args, &block
end

@private

# File rspec-mocks/lib/rspec/mocks/method_double.rb, line 230
def raise_method_not_stubbed_error
  RSpec::Mocks.error_generator.raise_method_not_stubbed_error(method_name)
end

@private

# File rspec-mocks/lib/rspec/mocks/method_double.rb, line 219
def remove_stub
  raise_method_not_stubbed_error if stubs.empty?
  remove_stub_if_present
end

@private

# File rspec-mocks/lib/rspec/mocks/method_double.rb, line 225
def remove_stub_if_present
  expectations.empty? ? reset : stubs.clear
end

@private

# File rspec-mocks/lib/rspec/mocks/method_double.rb, line 143
def reset
  restore_original_method
  clear
end

@private

# File rspec-mocks/lib/rspec/mocks/method_double.rb, line 106
def restore_original_method
  return unless @method_is_proxied

  remove_method_from_definition_target

  if @method_stasher.method_is_stashed?
    @method_stasher.restore
    restore_original_visibility
  end

  @method_is_proxied = false
rescue FrozenError
  return show_frozen_warning
end

@private

# File rspec-mocks/lib/rspec/mocks/method_double.rb, line 133
def restore_original_visibility
  method_owner.__send__(@original_visibility, @method_name)
end

@private

# File rspec-mocks/lib/rspec/mocks/method_double.rb, line 204
def setup_simple_method_double(method_name, response, collection, error_generator=nil, backtrace_line=nil)
  define_proxy_method

  me = SimpleMessageExpectation.new(method_name, response, error_generator, backtrace_line)
  collection.unshift me
  me
end

@private

# File rspec-mocks/lib/rspec/mocks/method_double.rb, line 122
def show_frozen_warning
  RSpec.warn_with(
    "WARNING: rspec-mocks was unable to restore the original `#{@method_name}` " \
    "method on #{@object.inspect} because it has been frozen.  If you reuse this " \
    "object, `#{@method_name}` will continue to respond with its stub implementation.",
    :call_site                      => nil,
    :use_spec_location_as_call_site => true
  )
end

@private

# File rspec-mocks/lib/rspec/mocks/method_double.rb, line 138
def verify
  expectations.each { |e| e.verify_messages_received }
end

@private

# File rspec-mocks/lib/rspec/mocks/method_double.rb, line 47
def visibility
  @proxy.visibility_for(@method_name)
end