rspec / rspec-mocks

RSpec's 'test double' framework, with support for stubbing and mocking
https://rspec.info
MIT License
1.16k stars 356 forks source link

Keyword argument expectation matching failing in Ruby 2.7 #1457

Closed marcotc closed 1 year ago

marcotc commented 2 years ago

Subject of the issue

When upgrading to 3.10.3 today, I noticed one test failure in our CI matrix that only affected Ruby 2.7.

This issue happens when we provide an empty keyword argument Hash as a keyword splat argument (method(**{})) to RSpec::Mocks::Matchers::Receive#with. Example: expect(obj).to receive(:call).with(:foo, **{}).

When later invoking obj.call(:foo, **{}) I receive the expected: (:foo, {}) got: (:foo) assertion error.

This does not affect Ruby < 2.7, nor Ruby > 2.7. Nor it affects rspec-mocks 3.10.2.

Preliminary investigation

Looking at the changelog for v3.10.3, I can see changes around ruby2_keywords, and it looks like the empty keyword argument {} get dropped in this invocation: https://github.com/rspec/rspec-mocks/blob/97c972be57f2c060a4a7fb8a3c5700a5ede693f0/lib/rspec/mocks/method_double.rb#L64

Another option is to filter out the empty hash before is gets stored in the args here: https://github.com/rspec/rspec-mocks/blob/97c972be57f2c060a4a7fb8a3c5700a5ede693f0/lib/rspec/mocks/matchers/receive_message_chain.rb#L18

I'm not sure which of these avenues is the correct one, though.

Your environment

Steps to reproduce

require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'
  gem 'rspec', '3.10.0'
  gem 'rspec-mocks', '3.10.3'
end

require 'rspec/autorun'

RSpec.describe do
  it do
    obj = Object.new
    expect(obj).to receive(:call).with(:foo, **{})
    obj.call(:foo, **{})
  end
end

Expected behavior

Assertion to match, and test to pass:

Finished in 0.00839 seconds (files took 0.08246 seconds to load)
1 example, 0 failures

Actual behavior

  1) is expected to receive call(:foo, {}) 1 time
     Failure/Error: obj.call(:foo, **{})

       #<Object:0x000055fe45517ba8> received :call with unexpected arguments
         expected: (:foo, {})
              got: (:foo)
     # test.rb:15:in `block (2 levels) in <main>'
pirj commented 2 years ago

Thanks for reporting. Would you like to take a stab at a pr with at least a failing spec?

What the change that resulted in the regression was supposed to fix?

Can’t tell upfront, but it may require a change on RSpec-support side. I believe there is an open ticket with some discussion regarding argument matching, do you mind checking if they are somewhat similar?

marcotc commented 1 year ago

This is fixed in 3.12.0, thank you so much!