rspec-mocks
rspec-mocks copied to clipboard
Stubbing prepended methods no longer works under Ruby 2.5.1
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
My guess is that it is related to this: https://github.com/ruby/ruby/commit/f4aea9108d19b304e8a7f9d2ec4763eadf4a64ba
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.
@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 https://github.com/ruby/ruby/commit/134967e5b1f29d4dbb4f0c35f582d328f3eaf9e7