123456789_123456789_123456789_123456789_123456789_

Class: RSpec::Mocks::AnyInstance::Recorder Private

Relationships & Source Files
Inherits: Object
Defined in: rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb

Overview

Given a class TheClass, TheClass.any_instance returns a Recorder, which records stubs and message expectations for later playback on instances of TheClass.

Further constraints are stored in instances of Chain.

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Constructor Details

.new(klass) ⇒ Recorder

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 16

def initialize(klass)
  @message_chains = MessageChains.new
  @stubs = Hash.new { |hash, key| hash[key] = [] }
  @observed_methods = []
  @played_methods = {}
  @backed_up_method_owner = {}
  @klass = klass
  @expectation_set = false

  return unless RSpec::Mocks.configuration.verify_partial_doubles?
  RSpec::Mocks.configuration.verifying_double_callbacks.each do |block|
    block.call(ObjectReference.for(klass))
  end
end

Instance Attribute Details

#klass (readonly)

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 14

attr_reader :message_chains, :stubs, :klass

#message_chains (readonly)

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 14

attr_reader :message_chains, :stubs, :klass

#stubs (readonly)

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 14

attr_reader :message_chains, :stubs, :klass

Instance Method Details

#allow_no_prepended_module_definition_of(_method_name) (private)

See additional method definition at line 284.

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 292

def allow_no_prepended_module_definition_of(method_name)
  prepended_modules = RSpec::Mocks::Proxy.prepended_modules_of(@klass)
  problem_mod = prepended_modules.find { |mod| mod.method_defined?(method_name) }
  return unless problem_mod

  AnyInstance.error_generator.raise_not_supported_with_prepend_error(method_name, problem_mod)
end

#already_observing?(method_name) ⇒ Boolean

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 127

def already_observing?(method_name)
  @observed_methods.include?(method_name) || super_class_observing?(method_name)
end

#ancestor_is_an_observer?(ancestor, method_name) ⇒ Boolean (private)

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 159

def ancestor_is_an_observer?(ancestor, method_name)
  return if ancestor == @klass

  ::RSpec::Mocks.space.
    any_instance_recorder_for(ancestor).already_observing?(method_name)
end

#backup_method!(method_name) (private)

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 235

def backup_method!(method_name)
  return unless public_protected_or_private_method_defined?(method_name)

  alias_method_name = build_alias_method_name(method_name)
  @backed_up_method_owner[method_name.to_sym] ||= @klass.instance_method(method_name).owner
  @klass.class_exec do
    alias_method alias_method_name, method_name
  end
end

#build_alias_method_name(method_name)

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 122

def build_alias_method_name(method_name)
  "__#{method_name}_without_any_instance__"
end

#expect_chain(*method_names_and_optional_return_values, &block)

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 53

def expect_chain(*method_names_and_optional_return_values, &block)
  @expectation_set = true
  normalize_chain(*method_names_and_optional_return_values) do |method_name, args|
    observe!(method_name)
    message_chains.add(method_name, ExpectChainChain.new(self, *args, &block))
  end
end

#instance_that_received(method_name)

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 117

def instance_that_received(method_name)
  @played_methods[method_name]
end

#mark_invoked!(method_name) (private)

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 271

def mark_invoked!(method_name)
  backup_method!(method_name)
  recorder = self
  @klass.__send__(:define_method, method_name) do |*_args, &_blk|
    invoked_instance = recorder.instance_that_received(method_name)
    inspect = "#<#{self.class}:#{object_id} #{instance_variables.map { |name| "#{name}=#{instance_variable_get name}" }.join(', ')}>"
    AnyInstance.error_generator.raise_message_already_received_by_other_instance_error(
      method_name, inspect, invoked_instance
    )
  end
end

#normalize_chain(*args) {|args.first, args| ... } (private)

Yields:

  • (args.first, args)
[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 178

def normalize_chain(*args)
  args.shift.to_s.split('.').map { |s| s.to_sym }.reverse.each { |a| args.unshift a }
  yield args.first, args
end

#notify_received_message(_object, message, args, _blk)

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 132

def notify_received_message(_object, message, args, _blk)
  has_expectation = false

  message_chains.each_unfulfilled_expectation_matching(message, *args) do |expectation|
    has_expectation = true
    expectation.expectation_fulfilled!
  end

  return unless has_expectation

  restore_method!(message)
  mark_invoked!(message)
end

#observe!(method_name) (private)

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 249

def observe!(method_name)
  allow_no_prepended_module_definition_of(method_name)

  if RSpec::Mocks.configuration.verify_partial_doubles? && !Mocks.configuration.temporarily_suppress_partial_double_verification
    unless public_protected_or_private_method_defined?(method_name)
      AnyInstance.error_generator.raise_does_not_implement_error(@klass, method_name)
    end
  end

  stop_observing!(method_name) if already_observing?(method_name)
  @observed_methods << method_name
  backup_method!(method_name)
  recorder = self
  method_was_private = @klass.private_method_defined?(method_name)
  @klass.__send__(:define_method, method_name) do |*args, &blk|
    recorder.playback!(self, method_name)
    __send__(method_name, *args, &blk)
  end
  @klass.__send__(:private, method_name) if method_was_private
  @klass.__send__(:ruby2_keywords, method_name) if @klass.respond_to?(:ruby2_keywords, true)
end

#playback!(instance, method_name)

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 109

def playback!(instance, method_name)
  RSpec::Mocks.space.ensure_registered(instance)
  message_chains.playback!(instance, method_name)
  @played_methods[method_name] = instance
  received_expected_message!(method_name) if message_chains.has_expectation?(method_name)
end

#public_protected_or_private_method_defined?(method_name) ⇒ Boolean (private)

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 245

def public_protected_or_private_method_defined?(method_name)
  MethodReference.method_defined_at_any_visibility?(@klass, method_name)
end

#received_expected_message!(method_name) (private)

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 183

def received_expected_message!(method_name)
  message_chains.received_expected_message!(method_name)
  restore_method!(method_name)
  mark_invoked!(method_name)
end

#remove_dummy_method!(method_name) (private)

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 229

def remove_dummy_method!(method_name)
  @klass.class_exec do
    remove_method method_name
  end
end

#restore_method!(method_name) (private)

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 189

def restore_method!(method_name)
  if public_protected_or_private_method_defined?(build_alias_method_name(method_name))
    restore_original_method!(method_name)
  else
    remove_dummy_method!(method_name)
  end
end

#restore_original_method!(method_name) (private)

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 197

def restore_original_method!(method_name)
  return unless @klass.instance_method(method_name).owner == @klass

  alias_method_name = build_alias_method_name(method_name)
  @klass.class_exec(@backed_up_method_owner) do |backed_up_method_owner|
    remove_method method_name

    # A @klass can have methods implemented (see Method#owner) in @klass
    # or inherited from a superclass. In ruby 2.2 and earlier, we can copy
    # a method regardless of the 'owner' and restore it to @klass after
    # because a call to 'super' from @klass's copied method would end up
    # calling the original class's superclass's method.
    #
    # With the commit below, available starting in 2.3.0, ruby changed
    # this behavior and a call to 'super' from the method copied to @klass
    # will call @klass's superclass method, which is the original
    # implementer of this method!  This leads to very strange errors
    # if @klass's copied method calls 'super', since it would end up
    # calling itself, the original method implemented in @klass's
    # superclass.
    #
    # For ruby 2.3 and above, we need to only restore methods that
    # @klass originally owned.
    #
    # https://github.com/ruby/ruby/commit/c8854d2ca4be9ee6946e6d17b0e17d9ef130ee81
    if RUBY_VERSION < "2.3" || backed_up_method_owner[method_name.to_sym] == self
      alias_method method_name, alias_method_name
    end
    remove_method alias_method_name
  end
end

#should_not_receive(method_name, &block)

The opposite of #should_receive

See Also:

  • Methods#should_not_receive
[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 75

def should_not_receive(method_name, &block)
  should_receive(method_name, &block).never
end

#should_receive(method_name, &block)

Initializes the recording a message expectation to be played back against any instance of this object that invokes the submitted method.

See Also:

  • Methods#should_receive
[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 66

def should_receive(method_name, &block)
  @expectation_set = true
  observe!(method_name)
  message_chains.add(method_name, PositiveExpectationChain.new(self, method_name, &block))
end

#stop_all_observation!

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 104

def stop_all_observation!
  @observed_methods.each { |method_name| restore_method!(method_name) }
end

#stop_observing!(method_name) (protected)

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 148

def stop_observing!(method_name)
  restore_method!(method_name)
  @observed_methods.delete(method_name)
  super_class_observers_for(method_name).each do |ancestor|
    ::RSpec::Mocks.space.
      any_instance_recorder_for(ancestor).stop_observing!(method_name)
  end
end

#stub(method_name, &block)

Initializes the recording a stub to be played back against any instance of this object that invokes the submitted method.

See Also:

  • Methods#stub
[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 35

def stub(method_name, &block)
  observe!(method_name)
  message_chains.add(method_name, StubChain.new(self, method_name, &block))
end

#stub_chain(*method_names_and_optional_return_values, &block)

Initializes the recording a stub chain to be played back against any instance of this object that invokes the method matching the first argument.

See Also:

  • Methods#stub_chain
[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 45

def stub_chain(*method_names_and_optional_return_values, &block)
  normalize_chain(*method_names_and_optional_return_values) do |method_name, args|
    observe!(method_name)
    message_chains.add(method_name, StubChainChain.new(self, *args, &block))
  end
end

#super_class_observers_for(method_name) (private)

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 166

def super_class_observers_for(method_name)
  @klass.ancestors.select do |ancestor|
    ancestor_is_an_observer?(ancestor, method_name)
  end
end

#super_class_observing?(method_name) ⇒ Boolean (private)

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 172

def super_class_observing?(method_name)
  @klass.ancestors.any? do |ancestor|
    ancestor_is_an_observer?(ancestor, method_name)
  end
end

#unstub(method_name)

Removes any previously recorded stubs, stub_chains or message expectations that use method_name.

See Also:

  • Methods#unstub
[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 83

def unstub(method_name)
  unless @observed_methods.include?(method_name.to_sym)
    AnyInstance.error_generator.raise_method_not_stubbed_error(method_name)
  end
  message_chains.remove_stub_chains_for!(method_name)
  stubs[method_name].clear
  stop_observing!(method_name) unless message_chains.has_expectation?(method_name)
end

#verify

Used internally to verify that message expectations have been fulfilled.

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/any_instance/recorder.rb', line 96

def verify
  return unless @expectation_set
  return if message_chains.all_expectations_fulfilled?

  AnyInstance.error_generator.raise_second_instance_received_message_error(message_chains.unfulfilled_expectations)
end