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

Stubbing prepended methods no longer works under Ruby 2.5.1 #1213

Open
jhottenstein opened this issue Mar 29, 2018 · 3 comments · Fixed by #1218
Open

Stubbing prepended methods no longer works under Ruby 2.5.1 #1213

jhottenstein opened this issue Mar 29, 2018 · 3 comments · Fixed by #1218

Comments

@jhottenstein
Copy link
Contributor

jhottenstein commented Mar 29, 2018

We recently upgraded to ruby 2.5.1 and had a regression in our test suite. I isolated the failure to expecting a method to be called on a partial class double and wrote the following test case:

    RSpec.describe "A partial class mock" do
      it 'allows prepended methods to be mocked' do
        klass = Class.new do
          def self.meth1
            'hi'
          end
        end

        mod = Module.new do
          def meth1
            meth2
          end

          def meth2
            'hello'
          end
        end
        klass.singleton_class.prepend(mod)
        expect(klass).to receive(:meth2).and_call_original
        klass.meth1
      end
    end
$ rbenv local 2.5.0
$ bundle exec rspec spec/rspec/mocks/partial_double_spec.rb --example "A partial class mock allows prepended methods to be mocked"
Run options: include {:full_description=>/A\ partial\ class\ mock\ allows\ prepended\ methods\ to\ be\ mocked/}

Randomized with seed 51084

A partial class mock
  allows prepended methods to be mocked

Finished in 0.00733 seconds (files took 0.17655 seconds to load)
1 example, 0 failures

Randomized with seed 51084
$ rbenv local 2.5.1
$ bundle exec rspec spec/rspec/mocks/partial_double_spec.rb --example "A partial class mock allows prepended methods to be mocked"
Run options: include {:full_description=>/A\ partial\ class\ mock\ allows\ prepended\ methods\ to\ be\ mocked/}

Randomized with seed 20049

A partial class mock
  allows prepended methods to be mocked (FAILED - 1)

Failures:

  1) A partial class mock allows prepended methods to be mocked
     Failure/Error: object_singleton_class.__send__(@original_visibility, method_name)

     NameError:
       undefined method `meth2' for class `#<Class:#<Class:0x00007f82cd197538>>'
       Did you mean?  method
     # ./lib/rspec/mocks/method_double.rb:108:in `public'
     # ./lib/rspec/mocks/method_double.rb:108:in `restore_original_visibility'
     # ./lib/rspec/mocks/method_double.rb:87:in `restore_original_method'
     # ./lib/rspec/mocks/method_double.rb:118:in `reset'
     # ./lib/rspec/mocks/proxy.rb:319:in `block in reset'
     # ./lib/rspec/mocks/proxy.rb:319:in `each_value'
     # ./lib/rspec/mocks/proxy.rb:319:in `reset'
     # ./lib/rspec/mocks/space.rb:79:in `block in reset_all'
     # ./lib/rspec/mocks/space.rb:79:in `each_value'
     # ./lib/rspec/mocks/space.rb:79:in `reset_all'
     # ./lib/rspec/mocks.rb:52:in `teardown'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/rspec-core-a8aae27114dd/lib/rspec/core/mocking_adapters/rspec.rb:27:in `teardown_mocks_for_rspec'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/rspec-core-a8aae27114dd/lib/rspec/core/example.rb:510:in `run_after_example'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/rspec-core-a8aae27114dd/lib/rspec/core/example.rb:273:in `block in run'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/rspec-core-a8aae27114dd/lib/rspec/core/example.rb:500:in `block in with_around_and_singleton_context_hooks'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/rspec-core-a8aae27114dd/lib/rspec/core/example.rb:457:in `block in with_around_example_hooks'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/rspec-core-a8aae27114dd/lib/rspec/core/hooks.rb:464:in `block in run'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/rspec-core-a8aae27114dd/lib/rspec/core/hooks.rb:602:in `run_around_example_hooks_for'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/rspec-core-a8aae27114dd/lib/rspec/core/hooks.rb:464:in `run'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/rspec-core-a8aae27114dd/lib/rspec/core/example.rb:457:in `with_around_example_hooks'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/rspec-core-a8aae27114dd/lib/rspec/core/example.rb:500:in `with_around_and_singleton_context_hooks'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/rspec-core-a8aae27114dd/lib/rspec/core/example.rb:251:in `run'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/rspec-core-a8aae27114dd/lib/rspec/core/example_group.rb:629:in `block in run_examples'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/rspec-core-a8aae27114dd/lib/rspec/core/example_group.rb:625:in `map'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/rspec-core-a8aae27114dd/lib/rspec/core/example_group.rb:625:in `run_examples'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/rspec-core-a8aae27114dd/lib/rspec/core/example_group.rb:591:in `run'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/rspec-core-a8aae27114dd/lib/rspec/core/runner.rb:116:in `block (3 levels) in run_specs'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/rspec-core-a8aae27114dd/lib/rspec/core/runner.rb:116:in `map'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/rspec-core-a8aae27114dd/lib/rspec/core/runner.rb:116:in `block (2 levels) in run_specs'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/rspec-core-a8aae27114dd/lib/rspec/core/configuration.rb:1975:in `with_suite_hooks'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/rspec-core-a8aae27114dd/lib/rspec/core/runner.rb:111:in `block in run_specs'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/rspec-core-a8aae27114dd/lib/rspec/core/reporter.rb:74:in `report'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/rspec-core-a8aae27114dd/lib/rspec/core/runner.rb:110:in `run_specs'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/rspec-core-a8aae27114dd/lib/rspec/core/runner.rb:87:in `run'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/rspec-core-a8aae27114dd/lib/rspec/core/runner.rb:71:in `run'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/rspec-core-a8aae27114dd/lib/rspec/core/runner.rb:45:in `invoke'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bundler/gems/rspec-core-a8aae27114dd/exe/rspec:4:in `<top (required)>'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bin/rspec:23:in `load'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/bin/rspec:23:in `<top (required)>'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bundler-1.16.1/lib/bundler/cli/exec.rb:75:in `load'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bundler-1.16.1/lib/bundler/cli/exec.rb:75:in `kernel_load'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bundler-1.16.1/lib/bundler/cli/exec.rb:28:in `run'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bundler-1.16.1/lib/bundler/cli.rb:424:in `exec'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bundler-1.16.1/lib/bundler/vendor/thor/lib/thor/command.rb:27:in `run'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bundler-1.16.1/lib/bundler/vendor/thor/lib/thor/invocation.rb:126:in `invoke_command'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bundler-1.16.1/lib/bundler/vendor/thor/lib/thor.rb:387:in `dispatch'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bundler-1.16.1/lib/bundler/cli.rb:27:in `dispatch'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bundler-1.16.1/lib/bundler/vendor/thor/lib/thor/base.rb:466:in `start'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bundler-1.16.1/lib/bundler/cli.rb:18:in `start'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bundler-1.16.1/exe/bundle:30:in `block in <top (required)>'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bundler-1.16.1/lib/bundler/friendly_errors.rb:122:in `with_friendly_errors'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bundler-1.16.1/exe/bundle:22:in `<top (required)>'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/bin/bundle:23:in `load'
     # /Users/jhottenstein/.rbenv/versions/2.5.1/bin/bundle:23:in `<main>'

Finished in 0.00887 seconds (files took 0.18807 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/rspec/mocks/partial_double_spec.rb:271 # A partial class mock allows prepended methods to be mocked

Randomized with seed 20049
@jhottenstein
Copy link
Contributor Author

My guess is that it is related to this: ruby/ruby@f4aea91

@myronmarston
Copy link
Member

Thanks for reporting this. Seems like it's a regression in ruby 2.5.1 and there's a fix coming. Given the potentially large amount of effort to work around this I doubt we'll get around to it.

@maschwenk
Copy link

maschwenk commented Apr 5, 2018

@myronmarston I actually got the same thing when I upgraded to Ruby 2.3.7 from 2.3.6 just FYI. Looking now it seems its a backport from trunk? Yep, backported to 2.3, 2.4, 2.5 ruby/ruby@134967e

godfat added a commit to godfat/muack that referenced this issue Apr 9, 2018
godfat added a commit to godfat/rspec-mocks that referenced this issue Apr 10, 2018
Previously, we're assuming the method must be defined in the
singleton class. However this is not always true. Whenever the
method was only defined in the prepended module, then it's not
defined in the singleton class. We need to find the owner of
the method instead, which is the prepended module.

Closes rspec#1213
godfat added a commit to godfat/rspec-mocks that referenced this issue Apr 10, 2018
Previously, we're assuming the method must be defined in the
singleton class. However this is not always true. Whenever the
method was only defined in the prepended module, then it's not
defined in the singleton class. We need to find the owner of
the method instead, which is the prepended module.

Closes rspec#1213
godfat added a commit to godfat/rspec-mocks that referenced this issue Apr 10, 2018
Previously, we're assuming the method must be defined in the
singleton class. However this is not always true. Whenever the
method was only defined in the prepended module, then it's not
defined in the singleton class. We need to find the owner of
the method instead, which is the prepended module.

Closes rspec#1213
godfat added a commit to godfat/rspec-mocks that referenced this issue Apr 10, 2018
Previously, we're assuming the method must be defined in the
singleton class. However this is not always true. Whenever the
method was only defined in the prepended module, then it's not
defined in the singleton class. We need to find the owner of
the method instead, which is the prepended module.

Closes rspec#1213
mysociety-pusher pushed a commit to mysociety/whatdotheyknow-theme that referenced this issue Oct 29, 2019
alias_method_chain was deprecated in Rails 5.0 and removed in 5.1 [1]

Class methods are spilt between two modules because of a RSpec mock
issue [2]. We're using:
  - include: to allow the `alert_survey` method to still be mocked in
    our specs
  - prepend: to allows us to override a method and call `super` to run
    the original implementation of the method in Alaveteli core.

[1] rails/rails#19434
[2] rspec/rspec-mocks#1213
gbp pushed a commit to mysociety/whatdotheyknow-theme that referenced this issue Nov 1, 2019
alias_method_chain was deprecated in Rails 5.0 and removed in 5.1 [1]

Class methods are spilt between two modules because of a RSpec mock
issue [2]. We're using:
  - include: to allow the `alert_survey` method to still be mocked in
    our specs
  - prepend: to allows us to override a method and call `super` to run
    the original implementation of the method in Alaveteli core.

[1] rails/rails#19434
[2] rspec/rspec-mocks#1213
godfat added a commit to godfat/rspec-mocks that referenced this issue Oct 7, 2020
Previously, we're assuming the method must be defined in the
singleton class. However this is not always true. Whenever the
method was only defined in the prepended module, then it's not
defined in the singleton class. We need to find the owner of
the method instead, which is the prepended module.

Closes rspec#1213
godfat added a commit to godfat/rspec-mocks that referenced this issue Oct 7, 2020
Previously, we're assuming the method must be defined in the
singleton class. However this is not always true. Whenever the
method was only defined in the prepended module, then it's not
defined in the singleton class. We need to find the owner of
the method instead, which is the prepended module.

Closes rspec#1213
godfat added a commit to godfat/rspec-mocks that referenced this issue Oct 7, 2020
Previously, we're assuming the method must be defined in the
singleton class. However this is not always true. Whenever the
method was only defined in the prepended module, then it's not
defined in the singleton class. We need to find the owner of
the method instead, which is the prepended module.

Closes rspec#1213
godfat added a commit to godfat/rspec-mocks that referenced this issue Oct 23, 2020
Previously, we're assuming the method must be defined in the
singleton class. However this is not always true. Whenever the
method was only defined in the prepended module, then it's not
defined in the singleton class. We need to find the owner of
the method instead, which is the prepended module.

Closes rspec#1213
godfat added a commit to godfat/rspec-mocks that referenced this issue Oct 23, 2020
Previously, we're assuming the method must be defined in the
singleton class. However this is not always true. Whenever the
method was only defined in the prepended module, then it's not
defined in the singleton class. We need to find the owner of
the method instead, which is the prepended module.

Closes rspec#1213
godfat added a commit to godfat/rspec-mocks that referenced this issue Oct 23, 2020
Previously, we're assuming the method must be defined in the
singleton class. However this is not always true. Whenever the
method was only defined in the prepended module, then it's not
defined in the singleton class. We need to find the owner of
the method instead, which is the prepended module.

Closes rspec#1213
godfat added a commit to godfat/rspec-mocks that referenced this issue Dec 6, 2020
Previously, we're assuming the method must be defined in the
singleton class. However this is not always true. Whenever the
method was only defined in the prepended module, then it's not
defined in the singleton class. We need to find the owner of
the method instead, which is the prepended module.

Closes rspec#1213
texpert pushed a commit to mejuri-inc/rspec-mocks that referenced this issue Dec 10, 2020
Previously, we're assuming the method must be defined in the
singleton class. However this is not always true. Whenever the
method was only defined in the prepended module, then it's not
defined in the singleton class. We need to find the owner of
the method instead, which is the prepended module.

Closes rspec#1213
texpert pushed a commit to mejuri-inc/rspec-mocks that referenced this issue Dec 10, 2020
Previously, we're assuming the method must be defined in the
singleton class. However this is not always true. Whenever the
method was only defined in the prepended module, then it's not
defined in the singleton class. We need to find the owner of
the method instead, which is the prepended module.

Closes rspec#1213
pirj pushed a commit to godfat/rspec-mocks that referenced this issue Feb 2, 2021
Previously, we're assuming the method must be defined in the
singleton class. However this is not always true. Whenever the
method was only defined in the prepended module, then it's not
defined in the singleton class. We need to find the owner of
the method instead, which is the prepended module.

Closes rspec#1213
godfat added a commit to godfat/rspec-mocks that referenced this issue Mar 16, 2021
Previously, we're assuming the method must be defined in the
singleton class. However this is not always true. Whenever the
method was only defined in the prepended module, then it's not
defined in the singleton class. We need to find the owner of
the method instead, which is the prepended module.

Closes rspec#1213
pirj pushed a commit to godfat/rspec-mocks that referenced this issue Mar 16, 2021
Previously, we're assuming the method must be defined in the
singleton class. However this is not always true. Whenever the
method was only defined in the prepended module, then it's not
defined in the singleton class. We need to find the owner of
the method instead, which is the prepended module.

Closes rspec#1213
godfat added a commit to godfat/rspec-mocks that referenced this issue Apr 24, 2021
Previously, we're assuming the method must be defined in the
singleton class. However this is not always true. Whenever the
method was only defined in the prepended module, then it's not
defined in the singleton class. We need to find the owner of
the method instead, which is the prepended module.

Closes rspec#1213
pirj pushed a commit to godfat/rspec-mocks that referenced this issue Apr 28, 2021
Previously, we're assuming the method must be defined in the
singleton class. However this is not always true. Whenever the
method was only defined in the prepended module, then it's not
defined in the singleton class. We need to find the owner of
the method instead, which is the prepended module.

Closes rspec#1213
yujinakayama pushed a commit to yujinakayama/rspec-monorepo that referenced this issue Oct 19, 2021
Previously, we're assuming the method must be defined in the
singleton class. However this is not always true. Whenever the
method was only defined in the prepended module, then it's not
defined in the singleton class. We need to find the owner of
the method instead, which is the prepended module.

Closes rspec/rspec-mocks#1213

---
This commit was imported from rspec/rspec-mocks@604dc95.
emersonian pushed a commit to emersonian/rspec-mocks that referenced this issue Jul 5, 2022
Previously, we're assuming the method must be defined in the
singleton class. However this is not always true. Whenever the
method was only defined in the prepended module, then it's not
defined in the singleton class. We need to find the owner of
the method instead, which is the prepended module.

Closes rspec#1213
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

Successfully merging a pull request may close this issue.

3 participants