diff --git a/lib/mocha/parameter_matchers.rb b/lib/mocha/parameter_matchers.rb index 7a64ad6dd..d18fcf9c2 100644 --- a/lib/mocha/parameter_matchers.rb +++ b/lib/mocha/parameter_matchers.rb @@ -13,6 +13,7 @@ module ParameterMatchers; end require 'mocha/parameter_matchers/has_entry' require 'mocha/parameter_matchers/has_entries' require 'mocha/parameter_matchers/has_key' +require 'mocha/parameter_matchers/has_keys' require 'mocha/parameter_matchers/has_value' require 'mocha/parameter_matchers/includes' require 'mocha/parameter_matchers/instance_of' diff --git a/lib/mocha/parameter_matchers/has_keys.rb b/lib/mocha/parameter_matchers/has_keys.rb new file mode 100644 index 000000000..a0bb2b71e --- /dev/null +++ b/lib/mocha/parameter_matchers/has_keys.rb @@ -0,0 +1,53 @@ +require 'mocha/parameter_matchers/base' + +module Mocha + module ParameterMatchers + # Matches +Hash+ containing +keys+. + # + # @param [Object] keys expected keys. + # @return [HasKeys] parameter matcher. + # + # @see Expectation#with + # + # @example Actual parameter contains entry with expected keys. + # object = mock() + # object.expects(:method_1).with(has_keys(:key_1, :key_2)) + # object.method_1(:key_1 => 1, :key_2 => 2, :key_3 => 3) + # # no error raised + # + # @example Actual parameter does not contain entry with all expected keys. + # object = mock() + # object.expects(:method_1).with(has_keys(:key_1, :key_2)) + # object.method_1(:key_2 => 2) + # # error raised, because method_1 was not called with Hash containing key: :key_1 + # + def has_keys(*keys) # rubocop:disable Naming/PredicateName + HasKeys.new(*keys) + end + + # Parameter matcher which matches when actual parameter contains +Hash+ entry with expected keys. + class HasKeys < Base + # @private + def initialize(*keys) + raise ArgumentError, 'Argument has no keys.' if keys.empty? + + @keys = keys + end + + # @private + def matches?(available_parameters) + parameter = available_parameters.shift + return false unless parameter.respond_to?(:keys) + + @keys.map(&:to_matcher).all? do |matcher| + parameter.keys.any? { |key| matcher.matches?([key]) } + end + end + + # @private + def mocha_inspect + "has_keys(#{@keys.mocha_inspect[1...-1]})" + end + end + end +end diff --git a/test/unit/parameter_matchers/has_keys_test.rb b/test/unit/parameter_matchers/has_keys_test.rb new file mode 100644 index 000000000..00940f7c0 --- /dev/null +++ b/test/unit/parameter_matchers/has_keys_test.rb @@ -0,0 +1,64 @@ +require File.expand_path('../../../test_helper', __FILE__) + +require 'mocha/parameter_matchers/has_keys' +require 'mocha/parameter_matchers/instance_methods' +require 'mocha/inspect' + +class HasKeysTest < Mocha::TestCase + include Mocha::ParameterMatchers + + def test_should_match_hash_including_specified_keys + matcher = has_keys(:key_1, :key_2) + assert matcher.matches?([{ :key_1 => 1, :key_2 => 2, :key_3 => 3 }]) + end + + def test_should_not_match_hash_not_including_specified_keys + matcher = has_keys(:key_1, :key_2) + assert !matcher.matches?([{ :key_3 => 3 }]) + end + + def test_should_not_match_hash_not_including_all_keys + matcher = has_keys(:key_1, :key_2) + assert !matcher.matches?([{ :key_1 => 1, :key_3 => 3 }]) + end + + def test_should_describe_matcher + matcher = has_keys(:key_1, :key_2) + assert_equal 'has_keys(:key_1, :key_2)', matcher.mocha_inspect + end + + def test_should_match_hash_including_specified_key_with_nested_key_matcher + matcher = has_keys(equals(:key_1), equals(:key_2)) + assert matcher.matches?([{ :key_1 => 1, :key_2 => 2 }]) + end + + def test_should_not_match_hash_not_including_specified_keys_with_nested_key_matchers + matcher = has_keys(equals(:key_1), equals(:key2)) + assert !matcher.matches?([{ :key_2 => 2 }]) + end + + def test_should_not_raise_error_on_empty_arguments + matcher = has_keys(:key_1, :key_2) + assert_nothing_raised { matcher.matches?([]) } + end + + def test_should_not_match_on_empty_arguments + matcher = has_keys(:key_1, :key_2) + assert !matcher.matches?([]) + end + + def test_should_not_raise_error_on_argument_that_does_not_respond_to_keys + matcher = has_keys(:key_1, :key_2) + assert_nothing_raised { matcher.matches?([:key_1]) } + end + + def test_should_not_match_on_argument_that_does_not_respond_to_keys + matcher = has_keys(:key_1, :key_2) + assert !matcher.matches?([:key_1]) + end + + def test_should_raise_argument_error_if_no_keys_are_supplied + e = assert_raises(ArgumentError) { has_keys } + assert_equal 'Argument has no keys.', e.message + end +end