lib/rspec/mocks/any_instance/chain.rb



module RSpec
  module Mocks
    module AnyInstance
      class Chain
        def initialize(recorder, *args, &block)
          @recorder          = recorder
          @expectation_args  = args
          @expectation_block = block
          @source_line       = CallerFilter.first_non_rspec_line
          ensure_expectation_block_has_source_location
        end

        module Customizations
          # @macro [attach] record
          #   @method $1(*args, &block)
          #   Records the `$1` message for playback against an instance that
          #   invokes a method stubbed or mocked using `any_instance`.
          #
          #   @see RSpec::Mocks::MessageExpectation#$1
          #
          def self.record(method_name)
            class_eval(<<-EOM, __FILE__, __LINE__ + 1)
              def #{method_name}(*args, &block)
                record(:#{method_name}, *args, &block)
              end
            EOM
          end

          record :and_return
          record :and_raise
          record :and_throw
          record :and_yield
          record :and_call_original
          record :with
          record :once
          record :twice
          record :any_number_of_times
          record :exactly
          record :times
          record :never
          record :at_least
          record :at_most
        end

        include Customizations

        # @private
        def playback!(instance)
          message_expectation = create_message_expectation_on(instance)
          messages.inject(message_expectation) do |object, message|
            object.__send__(*message.first, &message.last)
          end
        end

        # @private
        def constrained_to_any_of?(*constraints)
          constraints.any? do |constraint|
            messages.any? do |message|
              message.first.first == constraint
            end
          end
        end

        # @private
        def expectation_fulfilled!
          @expectation_fulfilled = true
        end

        def never
          ErrorGenerator.raise_double_negation_error("expect_any_instance_of(MyClass)") if negated?
          super
        end

        private

        def create_message_expectation_on(instance)
          me = yield(::RSpec::Mocks.proxy_for(instance), IGNORED_BACKTRACE_LINE)

          if RSpec::Mocks.configuration.should_warn_about_any_instance_blocks?
            me.warn_about_receiver_passing(@source_line)
            me.display_any_instance_deprecation_warning_if_necessary(@expectation_block)
          elsif RSpec::Mocks.configuration.yield_receiver_to_any_instance_implementation_blocks?
            me.and_yield_receiver_to_implementation
          end

          me
        end

        def negated?
          messages.any? { |(message, *_), _| message.to_sym == :never }
        end

        def messages
          @messages ||= []
        end

        def last_message
          messages.last.first.first unless messages.empty?
        end

        def record(rspec_method_name, *args, &block)
          verify_invocation_order(rspec_method_name, *args, &block)
          messages << [args.unshift(rspec_method_name), block]
          self
        end

        if Proc.method_defined?(:source_location)
          def ensure_expectation_block_has_source_location; end
        else
          def ensure_expectation_block_has_source_location
            return unless @expectation_block
            source_location = CallerFilter.first_non_rspec_line.split(':')

            @expectation_block.extend Module.new {
              define_method(:source_location) { source_location }
            }
          end
        end
      end
    end
  end
end