class RSpec::Mocks::AnyInstance::Recorder

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](Chain).

@see AnyInstance @see Chain

Attributes

klass[R]

@private

message_chains[R]

@private

stubs[R]

@private

Public Class Methods

new(klass) click to toggle source
# File 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 = {}
  @klass = klass
  @expectation_set = false
end

Public Instance Methods

already_observing?(method_name) click to toggle source

@private

# File lib/rspec/mocks/any_instance/recorder.rb, line 123
def already_observing?(method_name)
  @observed_methods.include?(method_name) || super_class_observing?(method_name)
end
build_alias_method_name(method_name) click to toggle source

@private

# File lib/rspec/mocks/any_instance/recorder.rb, line 118
def build_alias_method_name(method_name)
  "__#{method_name}_without_any_instance__"
end
expect_chain(*method_names_and_optional_return_values, &block) click to toggle source

@private

# File lib/rspec/mocks/any_instance/recorder.rb, line 47
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) click to toggle source

@private

# File lib/rspec/mocks/any_instance/recorder.rb, line 113
def instance_that_received(method_name)
  @played_methods[method_name]
end
notify_received_message(_object, message, args, _blk) click to toggle source

@private

# File lib/rspec/mocks/any_instance/recorder.rb, line 128
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
playback!(instance, method_name) click to toggle source

@private

# File lib/rspec/mocks/any_instance/recorder.rb, line 105
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
should_not_receive(method_name, &block) click to toggle source

The opposite of `should_receive`

@see Methods#should_not_receive

# File lib/rspec/mocks/any_instance/recorder.rb, line 69
def should_not_receive(method_name, &block)
  should_receive(method_name, &block).never
end
should_receive(method_name, &block) click to toggle source

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

@see Methods#should_receive

# File lib/rspec/mocks/any_instance/recorder.rb, line 60
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!() click to toggle source

@private

# File lib/rspec/mocks/any_instance/recorder.rb, line 100
def stop_all_observation!
  @observed_methods.each { |method_name| restore_method!(method_name) }
end
stub(method_name, &block) click to toggle source

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

@see Methods#stub

# File lib/rspec/mocks/any_instance/recorder.rb, line 29
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) click to toggle source

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 Methods#stub_chain

# File lib/rspec/mocks/any_instance/recorder.rb, line 39
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
unstub(method_name) click to toggle source

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

@see Methods#unstub

# File lib/rspec/mocks/any_instance/recorder.rb, line 77
def unstub(method_name)
  unless @observed_methods.include?(method_name.to_sym)
    raise RSpec::Mocks::MockExpectationError, "The method `#{method_name}` was not stubbed or was already unstubbed"
  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() click to toggle source

@api private

Used internally to verify that message expectations have been fulfilled.

# File lib/rspec/mocks/any_instance/recorder.rb, line 90
def verify
  return unless @expectation_set
  return if message_chains.all_expectations_fulfilled?

  raise RSpec::Mocks::MockExpectationError,
        "Exactly one instance should have received the following "                  "message(s) but didn't: #{message_chains.unfulfilled_expectations.sort.join(', ')}"
end

Protected Instance Methods

stop_observing!(method_name) click to toggle source
# File lib/rspec/mocks/any_instance/recorder.rb, line 144
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

Private Instance Methods

allow_no_prepended_module_definition_of(method_name) click to toggle source
# File lib/rspec/mocks/any_instance/recorder.rb, line 250
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

  raise RSpec::Mocks::MockExpectationError,
        "Using `any_instance` to stub a method (#{method_name}) that has been "                    "defined on a prepended module (#{problem_mod}) is not supported."
end
ancestor_is_an_observer?(method_name) click to toggle source
# File lib/rspec/mocks/any_instance/recorder.rb, line 155
def ancestor_is_an_observer?(method_name)
  lambda do |ancestor|
    unless ancestor == @klass
      ::RSpec::Mocks.space.
        any_instance_recorder_for(ancestor).already_observing?(method_name)
    end
  end
end
backup_method!(method_name) click to toggle source
# File lib/rspec/mocks/any_instance/recorder.rb, line 208
def backup_method!(method_name)
  alias_method_name = build_alias_method_name(method_name)
  @klass.class_exec do
    alias_method alias_method_name, method_name
  end if public_protected_or_private_method_defined?(method_name)
end
mark_invoked!(method_name) click to toggle source
# File lib/rspec/mocks/any_instance/recorder.rb, line 239
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(', ')}>"
    raise RSpec::Mocks::MockExpectationError, "The message '#{method_name}' was received by #{inspect} but has already been received by #{invoked_instance}"
  end
end
normalize_chain(*args) { |first, args| ... } click to toggle source
# File lib/rspec/mocks/any_instance/recorder.rb, line 172
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
observe!(method_name) click to toggle source
# File lib/rspec/mocks/any_instance/recorder.rb, line 219
def observe!(method_name)
  allow_no_prepended_module_definition_of(method_name)

  if RSpec::Mocks.configuration.verify_partial_doubles?
    unless public_protected_or_private_method_defined?(method_name)
      raise MockExpectationError,
            "#{@klass} does not implement ##{method_name}"
    end
  end

  stop_observing!(method_name) if already_observing?(method_name)
  @observed_methods << method_name
  backup_method!(method_name)
  recorder = self
  @klass.__send__(:define_method, method_name) do |*args, &blk|
    recorder.playback!(self, method_name)
    __send__(method_name, *args, &blk)
  end
end
public_protected_or_private_method_defined?(method_name) click to toggle source
# File lib/rspec/mocks/any_instance/recorder.rb, line 215
def public_protected_or_private_method_defined?(method_name)
  MethodReference.method_defined_at_any_visibility?(@klass, method_name)
end
received_expected_message!(method_name) click to toggle source
# File lib/rspec/mocks/any_instance/recorder.rb, line 177
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) click to toggle source
# File lib/rspec/mocks/any_instance/recorder.rb, line 202
def remove_dummy_method!(method_name)
  @klass.class_exec do
    remove_method method_name
  end
end
restore_method!(method_name) click to toggle source
# File lib/rspec/mocks/any_instance/recorder.rb, line 183
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) click to toggle source
# File lib/rspec/mocks/any_instance/recorder.rb, line 191
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 do
    remove_method method_name
    alias_method method_name, alias_method_name
    remove_method alias_method_name
  end
end
super_class_observers_for(method_name) click to toggle source
# File lib/rspec/mocks/any_instance/recorder.rb, line 164
def super_class_observers_for(method_name)
  @klass.ancestors.select(&ancestor_is_an_observer?(method_name))
end
super_class_observing?(method_name) click to toggle source
# File lib/rspec/mocks/any_instance/recorder.rb, line 168
def super_class_observing?(method_name)
  @klass.ancestors.any?(&ancestor_is_an_observer?(method_name))
end