diff --git a/lib/mocha/parameter_matchers/has_entries.rb b/lib/mocha/parameter_matchers/has_entries.rb index 1b6d18d8..2b90f34b 100644 --- a/lib/mocha/parameter_matchers/has_entries.rb +++ b/lib/mocha/parameter_matchers/has_entries.rb @@ -30,20 +30,23 @@ def has_entries(entries) # rubocop:disable Naming/PredicateName # Parameter matcher which matches when actual parameter contains all expected +Hash+ entries. class HasEntries < Base # @private - def initialize(entries) + def initialize(entries, exact: false) @entries = entries + @exact = exact end # @private def matches?(available_parameters) parameter = available_parameters.shift + return false if @exact && @entries.length != parameter.length + has_entry_matchers = @entries.map { |key, value| HasEntry.new(key, value) } AllOf.new(*has_entry_matchers).matches?([parameter]) end # @private def mocha_inspect - "has_entries(#{@entries.mocha_inspect})" + @exact ? @entries.mocha_inspect : "has_entries(#{@entries.mocha_inspect})" end end end diff --git a/lib/mocha/parameter_matchers/positional_or_keyword_hash.rb b/lib/mocha/parameter_matchers/positional_or_keyword_hash.rb index d9b77c42..341d314b 100644 --- a/lib/mocha/parameter_matchers/positional_or_keyword_hash.rb +++ b/lib/mocha/parameter_matchers/positional_or_keyword_hash.rb @@ -1,6 +1,7 @@ require 'mocha/configuration' require 'mocha/deprecation' require 'mocha/parameter_matchers/base' +require 'mocha/parameter_matchers/has_entries' module Mocha module ParameterMatchers @@ -14,7 +15,7 @@ def initialize(value, expectation) def matches?(available_parameters) parameter, is_last_parameter = extract_parameter(available_parameters) - return false unless HasEntries.new(@value).matches?([parameter]) + return false unless HasEntries.new(@value, exact: true).matches?([parameter]) if is_last_parameter && !same_type_of_hash?(parameter, @value) return false if Mocha.configuration.strict_keyword_argument_matching? diff --git a/test/acceptance/parameter_matcher_test.rb b/test/acceptance/parameter_matcher_test.rb index d982539f..49cb5822 100644 --- a/test/acceptance/parameter_matcher_test.rb +++ b/test/acceptance/parameter_matcher_test.rb @@ -11,6 +11,24 @@ def teardown teardown_acceptance_test end + def test_should_match_hash_parameter_which_is_exactly_the_same + test_result = run_as_test do + mock = mock() + mock.expects(:method).with(key_1: 'value_1') + mock.method(key_1: 'value_1') + end + assert_passed(test_result) + end + + def test_should_not_match_hash_parameter_which_is_not_exactly_the_same + test_result = run_as_test do + mock = mock() + mock.expects(:method).with(key_1: 'value_1') + mock.method(key_1: 'value_1', key_2: 'value_2') + end + assert_failed(test_result) + end + def test_should_match_hash_parameter_with_specified_key test_result = run_as_test do mock = mock() @@ -137,6 +155,33 @@ def test_should_not_match_hash_parameter_with_specified_entries_using_nested_mat assert_failed(test_result) end + def test_should_match_hash_parameter_that_is_exactly_a_key_that_is_a_string_with_a_value_that_is_an_integer + test_result = run_as_test do + mock = mock() + mock.expects(:method).with(is_a(String) => is_a(Integer)) + mock.method('key_1' => 123) + end + assert_passed(test_result) + end + + def test_should_not_match_hash_parameter_that_is_exactly_a_key_that_is_a_string_with_a_value_that_is_an_integer_because_value_not_integer + test_result = run_as_test do + mock = mock() + mock.expects(:method).with(is_a(String) => is_a(Integer)) + mock.method('key_1' => '123') + end + assert_failed(test_result) + end + + def test_should_not_match_hash_parameter_that_is_exactly_a_key_that_is_a_string_with_a_value_that_is_an_integer_because_of_extra_entry + test_result = run_as_test do + mock = mock() + mock.expects(:method).with(is_a(String) => is_a(Integer)) + mock.method('key_1' => 123, 'key_2' => 'doesntmatter') + end + assert_failed(test_result) + end + def test_should_match_parameter_that_matches_any_value test_result = run_as_test do mock = mock() diff --git a/test/unit/parameter_matchers/positional_or_keyword_hash_test.rb b/test/unit/parameter_matchers/positional_or_keyword_hash_test.rb index 2db66c75..591a01b9 100644 --- a/test/unit/parameter_matchers/positional_or_keyword_hash_test.rb +++ b/test/unit/parameter_matchers/positional_or_keyword_hash_test.rb @@ -34,16 +34,32 @@ def test_should_match_hash_arg_with_hash_arg assert matcher.matches?([{ key_1: 1, key_2: 2 }]) end + def test_should_not_match_hash_arg_with_different_hash_arg + hash = { key_1: 1 } + matcher = build_matcher(hash) + assert !matcher.matches?([{ key_1: 1, key_2: 2 }]) + end + def test_should_match_keyword_args_with_keyword_args matcher = build_matcher(Hash.ruby2_keywords_hash({ key_1: 1, key_2: 2 })) # rubocop:disable Style/BracesAroundHashParameters assert matcher.matches?([Hash.ruby2_keywords_hash({ key_1: 1, key_2: 2 })]) # rubocop:disable Style/BracesAroundHashParameters end + def test_should_not_match_keyword_args_with_different_keyword_args + matcher = build_matcher(Hash.ruby2_keywords_hash({ key_1: 1 })) # rubocop:disable Style/BracesAroundHashParameters + assert !matcher.matches?([Hash.ruby2_keywords_hash({ key_1: 1, key_2: 2 })]) # rubocop:disable Style/BracesAroundHashParameters + end + def test_should_match_keyword_args_with_matchers_using_keyword_args matcher = build_matcher(Hash.ruby2_keywords_hash({ key_1: is_a(String), key_2: is_a(Integer) })) # rubocop:disable Style/BracesAroundHashParameters assert matcher.matches?([Hash.ruby2_keywords_hash({ key_1: 'foo', key_2: 2 })]) # rubocop:disable Style/BracesAroundHashParameters end + def test_should_not_match_keyword_args_with_matchers_using_keyword_args_when_not_all_entries_are_matched + matcher = build_matcher(Hash.ruby2_keywords_hash({ key_1: is_a(String) })) # rubocop:disable Style/BracesAroundHashParameters + assert !matcher.matches?([Hash.ruby2_keywords_hash({ key_1: 'foo', key_2: 2 })]) # rubocop:disable Style/BracesAroundHashParameters + end + def test_should_match_hash_arg_with_keyword_args_but_display_deprecation_warning_if_appropriate expectation = Mocha::Expectation.new(self, :foo); execution_point = ExecutionPoint.current matcher = build_matcher(Hash.ruby2_keywords_hash({ key_1: 1, key_2: 2 }), expectation) # rubocop:disable Style/BracesAroundHashParameters