module RSpec::Mocks::MessageExpectation::ImplementationDetails

@private Contains the parts of ‘MessageExpectation` that aren’t part of rspec-mocks’ public API. The class is very big and could really use some collaborators it delegates to for this stuff but for now this was the simplest way to split the public from private stuff to make it easier to publish the docs for the APIs we want published.

Attributes

argument_list_matcher[W]
error_generator[RW]
expected_from[W]
expected_received_count[W]
implementation[RW]
message[R]
orig_object[R]
type[R]

@private

Public Class Methods

new(error_generator, expectation_ordering, expected_from, method_double, type=:expectation, opts={}, &implementation_block) click to toggle source

rubocop:disable Metrics/ParameterLists

# File lib/rspec/mocks/message_expectation.rb, line 423
def initialize(error_generator, expectation_ordering, expected_from, method_double,
               type=:expectation, opts={}, &implementation_block)
  @type = type
  @error_generator = error_generator
  @error_generator.opts = error_generator.opts.merge(opts)
  @expected_from = expected_from
  @method_double = method_double
  @orig_object = @method_double.object
  @message = @method_double.method_name
  @actual_received_count = 0
  @actual_received_count_write_mutex = Support::Mutex.new
  @expected_received_count = type == :expectation ? 1 : :any
  @argument_list_matcher = ArgumentListMatcher::MATCH_ALL
  @order_group = expectation_ordering
  @order_group.register(self) unless type == :stub
  @expectation_type = type
  @ordered = false
  @at_least = @at_most = @exactly = nil

  self.invoking_internals = false

  # Initialized to nil so that we don't allocate an array for every
  # mock or stub. See also comment in `and_yield`.
  @args_to_yield = nil
  @eval_context = nil
  @yield_receiver_to_implementation_block = false

  @implementation = Implementation.new
  self.inner_implementation_action = implementation_block
end

Public Instance Methods

actual_received_count_matters?() click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 602
def actual_received_count_matters?
  @at_least || @at_most || @exactly
end
additional_expected_calls() click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 589
def additional_expected_calls
  return 0 if @expectation_type == :stub || !@exactly
  @expected_received_count - 1
end
advise(*args) click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 546
def advise(*args)
  similar_messages << args
end
and_yield_receiver_to_implementation() click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 459
def and_yield_receiver_to_implementation
  @yield_receiver_to_implementation_block = true
  self
end
called_max_times?() click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 501
def called_max_times?
  @expected_received_count != :any &&
    !@at_least &&
    @expected_received_count > 0 &&
    @actual_received_count >= @expected_received_count
end
description_for(verb) click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 578
def description_for(verb)
  @error_generator.describe_expectation(
    verb, @message, @expected_received_count,
    @actual_received_count, expected_args
  )
end
ensure_expected_ordering_received!() click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 521
def ensure_expected_ordering_received!
  @order_group.verify_invocation_order(self) if @ordered
  true
end
expectation_count_type() click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 572
def expectation_count_type
  return :at_least if @at_least
  return :at_most if @at_most
  nil
end
expected_args() click to toggle source

rubocop:enable Metrics/ParameterLists

# File lib/rspec/mocks/message_expectation.rb, line 455
def expected_args
  @argument_list_matcher.expected_args
end
expected_messages_received?() click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 517
def expected_messages_received?
  ignoring_args? || matches_exact_count? || matches_at_least_count? || matches_at_most_count?
end
generate_error() click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 554
def generate_error
  if similar_messages.empty?
    @error_generator.raise_expectation_error(
      @message, @expected_received_count, @argument_list_matcher,
      @actual_received_count, expectation_count_type, expected_args,
      @expected_from, exception_source_id
    )
  else
    @error_generator.raise_similar_message_args_error(
      self, @similar_messages, @expected_from
    )
  end
end
ignoring_args?() click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 526
def ignoring_args?
  @expected_received_count == :any
end
increase_actual_received_count!() click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 606
def increase_actual_received_count!
  @actual_received_count_write_mutex.synchronize do
    @actual_received_count += 1
  end
end
invoke(parent_stub, *args, &block) click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 478
def invoke(parent_stub, *args, &block)
  if invoking_internals
    safe_invoke_without_incrementing_received_count(parent_stub, *args, &block)
  else
    invoke_incrementing_actual_calls_by(1, true, parent_stub, *args, &block)
  end
end
invoke_without_incrementing_received_count(parent_stub, *args, &block) click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 492
def invoke_without_incrementing_received_count(parent_stub, *args, &block)
  invoke_incrementing_actual_calls_by(0, true, parent_stub, *args, &block)
end
matches?(message, *args) click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 468
def matches?(message, *args)
  @message == message && @argument_list_matcher.args_match?(*args)
end
matches_at_least_count?() click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 530
def matches_at_least_count?
  @at_least && @actual_received_count >= @expected_received_count
end
matches_at_most_count?() click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 534
def matches_at_most_count?
  @at_most && @actual_received_count <= @expected_received_count
end
matches_exact_count?() click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 538
def matches_exact_count?
  @expected_received_count == @actual_received_count
end
matches_name_but_not_args(message, *args) click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 508
def matches_name_but_not_args(message, *args)
  @message == message && !@argument_list_matcher.args_match?(*args)
end
negative?() click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 497
def negative?
  @expected_received_count == 0 && !@at_least
end
negative_expectation_for?(message) click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 598
def negative_expectation_for?(message)
  @message == message && negative?
end
ordered?() click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 594
def ordered?
  @ordered
end
raise_out_of_order_error() click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 585
def raise_out_of_order_error
  @error_generator.raise_out_of_order_error @message
end
raise_unexpected_message_args_error(args_for_multiple_calls) click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 568
def raise_unexpected_message_args_error(args_for_multiple_calls)
  @error_generator.raise_unexpected_message_args_error(self, args_for_multiple_calls, exception_source_id)
end
safe_invoke(parent_stub, *args, &block) click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 473
def safe_invoke(parent_stub, *args, &block)
  invoke_incrementing_actual_calls_by(1, false, parent_stub, *args, &block)
end
safe_invoke_without_incrementing_received_count(parent_stub, *args, &block) click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 487
def safe_invoke_without_incrementing_received_count(parent_stub, *args, &block)
  invoke_incrementing_actual_calls_by(0, false, parent_stub, *args, &block)
end
similar_messages() click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 542
def similar_messages
  @similar_messages ||= []
end
unadvise(args) click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 550
def unadvise(args)
  similar_messages.delete_if { |message| args.include?(message) }
end
verify_messages_received() click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 512
def verify_messages_received
  return if expected_messages_received?
  generate_error
end
yield_receiver_to_implementation_block?() click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 464
def yield_receiver_to_implementation_block?
  @yield_receiver_to_implementation_block
end

Private Instance Methods

exception_source_id() click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 614
def exception_source_id
  @exception_source_id ||= "#{self.class.name} #{__id__}"
end
has_been_invoked?() click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 666
def has_been_invoked?
  @actual_received_count > 0
end
initial_implementation_action=(action) click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 689
def initial_implementation_action=(action)
  implementation.initial_action = action
end
inner_implementation_action=(action) click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 693
def inner_implementation_action=(action)
  return unless action
  warn_about_stub_override if implementation.inner_action
  implementation.inner_action = action
end
invoke_incrementing_actual_calls_by(increment, allowed_to_fail, parent_stub, *args, &block) click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 633
def invoke_incrementing_actual_calls_by(increment, allowed_to_fail, parent_stub, *args, &block)
  self.invoking_internals = true

  args.unshift(orig_object) if yield_receiver_to_implementation_block?

  if negative? || (allowed_to_fail && (@exactly || @at_most) && (@actual_received_count == @expected_received_count))
    # args are the args we actually received, @argument_list_matcher is the
    # list of args we were expecting
    @error_generator.raise_expectation_error(
      @message, @expected_received_count,
      @argument_list_matcher,
      @actual_received_count + increment,
      expectation_count_type, args, nil, exception_source_id
    )
  end

  @order_group.handle_order_constraint self

  self.invoking_internals = false

  if implementation.present?
    implementation.call(*args, &block)
  elsif parent_stub
    parent_stub.invoke(nil, *args, &block)
  end
ensure
  self.invoking_internals = false
  @actual_received_count_write_mutex.synchronize do
    @actual_received_count += increment
  end
end
invoking_internals() click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 618
def invoking_internals
  RSpec::Support.thread_local_data[:"__rspec_#{object_id}_invoking_internals"]
end
invoking_internals=(value) click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 622
def invoking_internals=(value)
  # We clear the key for this rather than setting to false because otherwise the amount of
  # thread local data will keep growing over the lifetime of the thread (which is a long
  # time on a single threaded spec run e.g standard MRI)
  if value
    RSpec::Support.thread_local_data[:"__rspec_#{object_id}_invoking_internals"] = true
  else
    RSpec::Support.thread_local_data.delete(:"__rspec_#{object_id}_invoking_internals")
  end
end
raise_already_invoked_error_if_necessary(calling_customization) click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 670
def raise_already_invoked_error_if_necessary(calling_customization)
  return unless has_been_invoked?

  error_generator.raise_already_invoked_error(message, calling_customization)
end
set_expected_received_count(relativity, n) click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 676
def set_expected_received_count(relativity, n)
  raise "`count` is not supported with negative message expectations" if negative?
  @at_least = (relativity == :at_least)
  @at_most  = (relativity == :at_most)
  @exactly  = (relativity == :exactly)
  @expected_received_count = case n
                             when Numeric then n
                             when :once   then 1
                             when :twice  then 2
                             when :thrice then 3
                             end
end
terminal_implementation_action=(action) click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 699
def terminal_implementation_action=(action)
  implementation.terminal_action = action
end
warn_about_stub_override() click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 703
def warn_about_stub_override
  RSpec.warning(
    "You're overriding a previous stub implementation of `#{@message}`. " \
    "Called from #{CallerFilter.first_non_rspec_line}."
  )
end
wrap_original(method_name, &block) click to toggle source
# File lib/rspec/mocks/message_expectation.rb, line 710
def wrap_original(method_name, &block)
  if RSpec::Mocks::TestDouble === @method_double.object
    @error_generator.raise_only_valid_on_a_partial_double(method_name)
  else
    warn_about_stub_override if implementation.inner_action
    @implementation = AndWrapOriginalImplementation.new(@method_double.original_implementation_callable, block)
    @yield_receiver_to_implementation_block = false
  end

  nil
end