From 31cbd3cb7d03b04586ca2513446e8232f3360fc0 Mon Sep 17 00:00:00 2001 From: Yusuke Endoh Date: Thu, 7 Jan 2021 09:42:34 +0900 Subject: [PATCH] Respect Ruby 3's conversion from keywords to a positional last Hash This change allows a Hash argument expectation (`with({ a: 'a' })`) to accept keyword arguments (`foo(a: 'a')`) both in Ruby 2 and in Ruby 3. ``` allow(foo).to receive(:bar).with({ a: 'a' }) foo.bar(a: 'a') foo.bar({ a: 'a' }) ``` This is because Ruby 3 also allows the automatic conversion from keywords to a positional Hash. (But the conversion from a positional last Hash to keywords is not allowed.) ``` def foo(opts = {}) end foo(a: 'a') foo({ a: 'a' }) ``` --- lib/rspec/mocks/argument_list_matcher.rb | 2 +- spec/rspec/mocks/argument_matchers_spec.rb | 18 ++++-------------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/lib/rspec/mocks/argument_list_matcher.rb b/lib/rspec/mocks/argument_list_matcher.rb index 0f38c16f1..dfec28f8b 100644 --- a/lib/rspec/mocks/argument_list_matcher.rb +++ b/lib/rspec/mocks/argument_list_matcher.rb @@ -63,7 +63,7 @@ def args_match?(*actual_args) if Hash.respond_to?(:ruby2_keywords_hash?, true) # if both arguments end with Hashes, and if one is a keyword hash and the other is not, they don't match if Hash === expected_args.last && Hash === actual_args.last - if Hash.ruby2_keywords_hash?(actual_args.last) != Hash.ruby2_keywords_hash?(expected_args.last) + if !Hash.ruby2_keywords_hash?(actual_args.last) && Hash.ruby2_keywords_hash?(expected_args.last) return false end end diff --git a/spec/rspec/mocks/argument_matchers_spec.rb b/spec/rspec/mocks/argument_matchers_spec.rb index 7f71d97fe..3d0d051e9 100644 --- a/spec/rspec/mocks/argument_matchers_spec.rb +++ b/spec/rspec/mocks/argument_matchers_spec.rb @@ -381,20 +381,10 @@ def ==(other) a_double.random_call(:a => "a", :b => "b") end - if RSpec::Support::RubyFeatures.required_kw_args_supported? - it "fails to match against a hash submitted by reference and received by value in Ruby 3", :reset => true do - opts = {:a => "a", :b => "b"} - expect(a_double).to receive(:random_call).with(opts) - expect do - a_double.random_call(:a => "a", :b => "b") - end.to fail_with(/expected: \(\{:a=>"a", :b=>"b"|:b=>"b", :a=>"a"\}\)/) - end - else - it "matches against a hash submitted by reference and received by value in Ruby 2" do - opts = {:a => "a", :b => "b"} - expect(a_double).to receive(:random_call).with(opts) - a_double.random_call(:a => "a", :b => "b") - end + it "matches against a hash submitted by reference and received by value (in both Ruby 2 and Ruby 3)" do + opts = {:a => "a", :b => "b"} + expect(a_double).to receive(:random_call).with(opts) + a_double.random_call(:a => "a", :b => "b") end if RSpec::Support::RubyFeatures.required_kw_args_supported?