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

NoMethodError when receive and have_receive are aliased with alias_matcher. Conflicts with ActiveSupport's Object#with #1454

Open
pucinsk opened this issue Apr 8, 2024 · 3 comments

Comments

@pucinsk
Copy link

pucinsk commented Apr 8, 2024

Subject of the issue

Rails 7.1 added Object#with which conflicts with receive and have_received that define a #with method (chain :with) when used via an alias_matcher.

Related issue: #1437 (solved)

Your environment

  • Ruby version: 3.2.2
  • rspec-expectations version: 3.13.0
  • Rails version: 7.1.3.2

Steps to reproduce

require "rails_helper"

RSpec::Matchers.alias_matcher :have_received_alias, :have_received
RSpec::Matchers.alias_matcher :receive_alias, :receive

RSpec.describe "Foo" do
  it do
    expect(1).to have_received_alias(:foo_bar).with(foo: :bar)
  end

  it do
    expect(1).to receive_alias(:foo_bar).with(foo: :bar)
  end
end

Expected behavior

Neither assert should raise an NoMethodError

Actual behavior

Arguments passed for with method are treated as Object methods.

Failures:

  1) Foo 
     Failure/Error: expect(1).to receive_alias(:foo_bar).with(foo: :bar)
     
     NoMethodError:
       undefined method `foo' for #<RSpec::Mocks::Matchers::Receive:0x00000001107d1868>
     # ./spec/dummy_spec.rb:14:in `block (2 levels) in <top (required)>'
     # ./spec/rails_helper.rb:233:in `block (2 levels) in <top (required)>'
     # ./spec/spec_helper.rb:147:in `block (2 levels) in <top (required)>'

  2) Foo 
     Failure/Error: expect(1).to have_received_alias(:foo_bar).with(foo: :bar)
     
     NoMethodError:
       undefined method `foo' for #<RSpec::Mocks::Matchers::HaveReceived:0x0000000128478b68 @method_name=:foo_bar, @block=nil, @constraints=[], @subject=nil>
     # ./spec/dummy_spec.rb:10:in `block (2 levels) in <top (required)>'
     # ./spec/rails_helper.rb:233:in `block (2 levels) in <top (required)>'
     # ./spec/spec_helper.rb:147:in `block (2 levels) in <top (required)>'

Finished in 2.84 seconds (files took 10.64 seconds to load)
2 examples, 2 failures
@pirj
Copy link
Member

pirj commented Apr 8, 2024

I’m away from my computer. Wondering if ‘receive(:foo_bar)’ and ‘receive_alias(:foo_bar)’ would return instances of the same class? Why would there be such a difference?

@pucinsk
Copy link
Author

pucinsk commented Apr 9, 2024

It seems that something is wrong with RSpec::Matchers::AliasedMatcher matcher. ActiveSupport seems to override HaveReceived methods.

I'm pasting some output from debugging session what I found, it might be helpful.

[1] pry(#<RSpec::ExampleGroups::Foo>)> have_received_alias(:foo_bar).class
=> RSpec::Matchers::AliasedMatcher
[2] pry(#<RSpec::ExampleGroups::Foo>)> have_received_alias(:foo_bar).class.superclass
=> RSpec::Matchers::MatcherDelegator
[3] pry(#<RSpec::ExampleGroups::Foo>)> have_received(:foo_bar).class
=> RSpec::Mocks::Matchers::HaveReceived
[4] pry(#<RSpec::ExampleGroups::Foo>)> have_received(:foo_bar).class.superclass
=> Object
...
[7] pry(#<RSpec::ExampleGroups::Foo>)> have_received_alias(:foo_bar).inspect
=> "#<RSpec::Mocks::Matchers::HaveReceived:0x0000000162438768 @method_name=:foo_bar, @block=nil, @constraints=[], @subject=nil>"

[8] pry(#<RSpec::ExampleGroups::Foo>)> have_received(:foo_bar).inspect
=> "#<RSpec::Mocks::Matchers::HaveReceived:0x00000001624348e8 @method_name=:foo_bar, @block=nil, @constraints=[], @subject=nil>"

[6] pry(#<RSpec::ExampleGroups::Foo>)> show-method have_received_alias(:foo_bar).with

From: gems/ruby/3.2.0/gems/activesupport-7.1.3.2/lib/active_support/core_ext/object/with.rb:4:

[5] pry(#<RSpec::ExampleGroups::Foo>)> show-method have_received(:foo_bar).with
From: gems/ruby/3.2.0/gems/rspec-mocks-3.13.0/lib/rspec/mocks/matchers/have_received.rb:53:

My current workaround:

class RailsAliasedMatcherPatched < RSpec::Matchers::AliasedMatcherWithOperatorSupport
  def with(*, **)
    base_matcher.__send__(:with, *, **)
  end
end

RSpec::Matchers.alias_matcher :have_received_alias, :have_received, klass: RailsAliasedMatcherPatched
RSpec::Matchers.alias_matcher :receive_alias, :receive, klass: RailsAliasedMatcherPatched

@pirj
Copy link
Member

pirj commented Apr 11, 2024 via email

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

2 participants