123456789_123456789_123456789_123456789_123456789_

Class: RSpec::Mocks::ArgumentListMatcher

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

Overview

Wrapper for matching arguments against a list of expected values. Used by the ‘with` method on a MessageExpectation:

expect(object).to receive(:message).with(:a, 'b', 3)
object.message(:a, 'b', 3)

Values passed to ‘with` can be literal values or argument matchers that match against the real objects .e.g.

expect(object).to receive(:message).with(hash_including(:a => 'b'))

Can also be used directly to match the contents of any ‘Array`. This enables 3rd party mocking libs to take advantage of rspec’s argument matching without using the rest of rspec-mocks.

require 'rspec/mocks/argument_list_matcher'
include RSpec::Mocks::ArgumentMatchers

arg_list_matcher = RSpec::Mocks::ArgumentListMatcher.new(123, hash_including(:a => 'b'))
arg_list_matcher.args_match?(123, :a => 'b')

This class is immutable.

See Also:

Constant Summary

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Constructor Details

.new(*expected_args) ⇒ ArgumentListMatcher

Initializes an ‘ArgumentListMatcher` with a collection of literal values and/or argument matchers.

Parameters:

  • expected_args (Array)

    a list of expected literals and/or argument matchers

See Also:

[ GitHub ]

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

def initialize(*expected_args)
  @expected_args = expected_args
  ensure_expected_args_valid!
end

Instance Attribute Details

#expected_args (readonly)

This method is for internal use only.
[ GitHub ]

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

attr_reader :expected_args

Instance Method Details

#args_match?(*actual_args) ⇒ Boolean

Matches each element in the ‘expected_args` against the element in the same position of the arguments passed to .new.

Parameters:

  • actual_args (Array)

See Also:

  • #initialize
[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/argument_list_matcher.rb', line 58

def args_match?(*actual_args)
  expected_args = resolve_expected_args_based_on(actual_args)

  return false if expected_args.size != actual_args.size

  if RUBY_VERSION >= "3"
    # If the expectation was set with keywords, while the actual method was called with a positional hash argument, they don't match.
    # If the expectation was set without keywords, e.g., with({a: 1}), then it fine to call it with either foo(a: 1) or foo({a: 1}).
    # This corresponds to Ruby semantics, as if the method was def foo(options).
    if Hash === expected_args.last && Hash === actual_args.last
      if !Hash.ruby2_keywords_hash?(actual_args.last) && Hash.ruby2_keywords_hash?(expected_args.last)
        return false
      end
    end
  end

  Support::FuzzyMatcher.values_match?(expected_args, actual_args)
end

#ensure_expected_args_valid! (private)

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/argument_list_matcher.rb', line 100

def ensure_expected_args_valid!
  if expected_args.count { |a| ArgumentMatchers::AnyArgsMatcher::INSTANCE == a } > 1
    raise ArgumentError, "`any_args` can only be passed to " \
          "`with` once but you have passed it multiple times."
  elsif expected_args.count > 1 && expected_args.any? { |a| ArgumentMatchers::NoArgsMatcher::INSTANCE == a }
    raise ArgumentError, "`no_args` can only be passed as a " \
          "singleton argument to `with` (i.e. `with(no_args)`), " \
          "but you have passed additional arguments."
  end
end

#replace_any_args_with_splat_of_anything(before_count, actual_args_count) (private)

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/argument_list_matcher.rb', line 92

def replace_any_args_with_splat_of_anything(before_count, actual_args_count)
  any_args_count  = actual_args_count   - expected_args.count + 1
  after_count     = expected_args.count - before_count        - 1

  any_args = 1.upto(any_args_count).map { ArgumentMatchers::AnyArgMatcher::INSTANCE }
  expected_args.first(before_count) + any_args + expected_args.last(after_count)
end

#resolve_expected_args_based_on(actual_args)

This method is for internal use only.

Resolves abstract arg placeholders like ‘no_args` and any_args into a more concrete arg list based on the provided actual_args.

[ GitHub ]

  
# File 'rspec-mocks/lib/rspec/mocks/argument_list_matcher.rb', line 81

def resolve_expected_args_based_on(actual_args)
  return [] if [ArgumentMatchers::NoArgsMatcher::INSTANCE] == expected_args

  any_args_index = expected_args.index { |a| ArgumentMatchers::AnyArgsMatcher::INSTANCE == a }
  return expected_args unless any_args_index

  replace_any_args_with_splat_of_anything(any_args_index, actual_args.count)
end