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

Undefined method error in restore_original_visibility #2394

Open
ojab opened this issue Oct 5, 2020 · 11 comments
Open

Undefined method error in restore_original_visibility #2394

ojab opened this issue Oct 5, 2020 · 11 comments

Comments

@ojab
Copy link
Contributor

ojab commented Oct 5, 2020

Subject of the issue

It requires running rails application and I'm not that interested to minimize testcase more since I don't use ruby-2.2 and encountered this by making PR to another project.

require 'rails'
require 'rspec'

module Dummy
  class Application < Rails::Application
  end
end

Dummy::Application.initialize!



describe 'fail' do
  subject { {} }

  it do
    expect(subject).to receive(:except)
    subject.except
  end
end

Your environment

  • Ruby version: 2.2.10
  • rspec-mocks version: 3.9.1
  • rails version: 5.2.2

Steps to reproduce

bundle exec rspec /tmp/tst.rb

Expected behavior

No errors

Actual behavior

Failures:

  1) fail is expected to receive except(*(any args)) 1 time
     Failure/Error: object_singleton_class.__send__(@original_visibility, method_name)

     NameError:
       undefined method `except' for class `Hash'
     # /home/ojab/.rvm/gems/ruby-2.2.10/gems/rspec-mocks-3.9.1/lib/rspec/mocks/method_double.rb:108:in `public'
@JonRowe JonRowe transferred this issue from rspec/rspec-mocks Oct 5, 2020
@JonRowe
Copy link
Member

JonRowe commented Oct 5, 2020

Rails is messing with the hash here, causing a confusing error, this might need to be transferred back to rspec-mocks if its fixable.

@pirj
Copy link
Member

pirj commented Oct 5, 2020

Off the top of my head you need to require rails/all, or specifically active_support/core_ext/hash that defines Hash#extract before running the test.

Can you confirm that {}.except(:a) does not error out if you add it as the first line of your test example, @ojab ?

@ojab
Copy link
Contributor Author

ojab commented Oct 5, 2020

It fails only if expectation is set, so subject.except(:a) on the first line works.
I've tried to require rails in different combinations (including rails/all) trying to minimize it and the error is not reproducible, so rails app initialization is the key.

@pirj
Copy link
Member

pirj commented Oct 5, 2020

Less trouble - I can reproduce on Ruby 2.6.3 as well.

@pirj pirj changed the title undefined method except' for class Hash' when expect({}).to receive(:except) on ruby-2.2 Undefined method error in restore_original_visibility Oct 5, 2020
@pirj
Copy link
Member

pirj commented Oct 6, 2020

I could find a way to work this around:

      # @private
      def restore_original_visibility
        return unless @original_visibility &&
          MethodReference.method_defined_at_any_visibility?(object_singleton_class, @method_name)
+       return if MethodReference.instance_method_visibility_for(object_singleton_class, @method_name) == @original_visibility

but I couldn't understand what the root cause is.

And as @ojab you mention, it's surely caused by something that Dummy::Application.initialize! is doing.

@JonRowe By looking at RSpec::Mocks::MethodDouble I (probably naïvely) think that it can be refactored to always prepend a module instead of dealing with the singleton class directly, and remove_method from this module. This will automatically restore the visibility of the original method.
Does it make sense to work around now, and refactor sometimes later when we drop support for Rubies that don't support prepend?

@pirj
Copy link
Member

pirj commented Oct 7, 2020

Wondering if this is somehow related rspec/rspec-mocks#1218 and could be a better solution.
except is defined by reopening the class, no tricks.

@JonRowe
Copy link
Member

JonRowe commented Oct 9, 2020

The root issue is that the hash when we access it doesn't respond to the method but seems to.

@pirj
Copy link
Member

pirj commented Oct 9, 2020

It does AFAIR, you can call except on {} with no issue.

@JonRowe
Copy link
Member

JonRowe commented Oct 9, 2020

Sure, you can, but not in this particular scenario.

Probably because the hash is evaluated when the reproduction is loaded, and is thus before Rails installs its extensions.
This is why manually requiring extensions instead fixes it.

@JonRowe
Copy link
Member

JonRowe commented Oct 9, 2020

As with all such issues with rails, you need to have loaded the extensions before parsing literals.

@fledman
Copy link

fledman commented Jan 7, 2021

@pirj I believe this is the root cause: rspec/rspec-mocks#1395

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