Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Message expectation with block arguments for keyword argument method behaves differently #1486

Open
TonyCTHsu opened this issue Sep 14, 2022 · 3 comments

Comments

@TonyCTHsu
Copy link

Subject of the issue

Message expectation with block arguments for keyword argument method behaves differently for ruby 3.2.0preview2.

The code snippets works fine with ruby 3.0.3 and ruby 3.1.1. I wasn't sure what is the root cause.

Your environment

  • Ruby version: ruby 3.2.0preview2 (2022-09-09 master 35cfc9a3bb) [x86_64-linux]
  • rspec-mocks version: 3.11.1

Steps to reproduce

RSpec.describe do
  class TestObject
    def initialize(**kwargs)
    end
  end
  it 'double splat block args' do
    expect(TestObject).to receive(:new) do |**opts|
      expect(opts).to eq(foo: 'bar')
    end

    hash = { foo: 'bar' }
    TestObject.new(**hash)
  end

  it 'keyword block args' do
    expect(TestObject).to receive(:new) do |foo:, **_|
      expect(foo).to eq('bar')
    end

    hash = { foo: 'bar' }
    TestObject.new(**hash)
  end
end

Expected behavior

The test passes like

  • ruby 3.0.3
  • ruby 3.1.1

Actual behavior

Failures:

  1) double splat block args
     Failure/Error: expect(opts).to eq(foo: 'bar')

       expected: {:foo=>"bar"}
            got: {}

       (compared using ==)

       Diff:
       @@ -1,2 +1 @@
       -:foo => "bar",
  2) keyword block args
     Failure/Error:
       expect(TestObject).to receive(:new) do |foo:, **_|
         expect(foo).to eq('bar')
       end

     ArgumentError:
       missing keyword: :foo
@pirj
Copy link
Member

pirj commented Sep 14, 2022

Does it work the same way with other methods, not initialize/new?

@JonRowe
Copy link
Member

JonRowe commented Sep 20, 2022

Interestingly I tried this on Mac (ruby 3.2.0preview2 (2022-09-09 master 35cfc9a3bb) [arm64-darwin21]) and it passed, so I'm inclined to close this for now as a Ruby bug especially as its not an expected change in behaviour...

@ivoanjo
Copy link

ivoanjo commented Sep 21, 2022

Hey @pirj and @JonRowe thanks for the feedback! I work with @TonyCTHsu and he's off for a few weeks, so I can jump in and provide the extra information.

Does it work the same way with other methods, not initialize/new?

Yes! See my provided extended example below.

Interestingly I tried this on Mac (ruby 3.2.0preview2 (2022-09-09 master 35cfc9a3bb) [arm64-darwin21]) and it passed, so I'm inclined to close this for now as a Ruby bug especially as its not an expected change in behaviour...

I suspect it may be a deliberate incompatible Ruby change, as the 3.2.0-preview2 release notes mention it https://www.ruby-lang.org/en/news/2022/09/09/ruby-3-2-0-preview2-released/ ("Methods taking a rest parameter (like *args) and wishing to delegate keyword arguments through foo(*args) must now be marked with ruby2_keywords (if not already the case). [...]").

You are right re: the reproduction, I remembered Tony showing me the issue and when I just re-ran it locally I was actually not seeing it trigger.

It turns out the reproduction example is incomplete. In particular, mocks.verify_partial_doubles = true needs to be set in the configuration to trigger the issue -- without it, the issue does not trigger.

Here is an extended example that is self-contained and reproduces on my machine.
I can reproduce the issue with:

  • ruby 3.2.0preview2 (2022-09-09 master 35cfc9a3bb) [x86_64-darwin20]
  • ruby 3.2.0dev (2022-09-21T13:59:45Z master c21f820b49) [x86_64-darwin20] (freshly compiled today)
  • ruby 3.2.0preview2 (2022-09-09 master 35cfc9a3bb) [x86_64-linux]
  • ruby 3.2.0preview2 (2022-09-09 master 35cfc9a3bb) [aarch64-linux]

so it seems to affect every release at least >= 3.2.0-preview2.

Full reproduction:

require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'

  gem 'rspec', '= 3.11.0'
end

require 'rspec/autorun'

puts RUBY_DESCRIPTION

RSpec.configure do |config|
  config.mock_with :rspec do |mocks|
    mocks.verify_partial_doubles = true # changing this to false makes the issue go away
  end
end

RSpec.describe do
  class TestObject
    def initialize(**kwargs)
    end

    def foo(**kwargs)
    end
  end

  context 'TestObject.new' do
    it 'double splat block args' do
      expect(TestObject).to receive(:new) do |**opts|
        expect(opts).to eq(foo: 'bar')
      end

      hash = { foo: 'bar' }
      TestObject.new(**hash)
    end

    it 'keyword block args' do
      expect(TestObject).to receive(:new) do |foo:, **_|
        expect(foo).to eq('bar')
      end

      hash = { foo: 'bar' }
      TestObject.new(**hash)
    end
  end

  context 'TestObject#foo' do
    let(:instance) { TestObject.new }

    it 'double splat block args' do
      expect(instance).to receive(:foo) do |**opts|
        expect(opts).to eq(foo: 'bar')
      end

      hash = { foo: 'bar' }
      instance.foo(**hash)
    end

    it 'keyword block args' do
      expect(instance).to receive(:foo) do |foo:, **_|
        expect(foo).to eq('bar')
      end

      hash = { foo: 'bar' }
      instance.foo(**hash)
    end
  end
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants