Skip to content
This repository was archived by the owner on Nov 30, 2024. It is now read-only.

Commit 604dc95

Browse files
godfatpirj
authored andcommitted
Fix stubbing prepended only methods
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 #1213
1 parent a692dd4 commit 604dc95

File tree

4 files changed

+47
-7
lines changed

4 files changed

+47
-7
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ Bug Fixes:
2121

2222
* Support keyword argument semantics when constraining argument expectations using
2323
`with` on Ruby 3.0+ (Yusuke Endoh, #1394)
24+
* Fix stubbing of prepended-only methods. (Lin Jen-Shin, #1218)
2425

2526
### 3.10.2 / 2021-01-27
2627
[Full Changelog](http://github.com/rspec/rspec-mocks/compare/v3.10.1...v3.10.2)

lib/rspec/mocks/instance_method_stasher.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,12 @@ def method_owned_by_klass?
6262
# `#<MyClass:0x007fbb94e3cd10>`, rather than the expected `MyClass`.
6363
owner = owner.class unless Module === owner
6464

65-
owner == @klass || !(method_defined_on_klass?(owner))
65+
owner == @klass ||
66+
# When `extend self` is used, and not under `allow_any_instance_of`
67+
# nor `expect_any_instance_of`.
68+
(owner.singleton_class == @klass &&
69+
!Mocks.space.any_instance_recorder_for(owner, true)) ||
70+
!(method_defined_on_klass?(owner))
6671
end
6772
end
6873
end

lib/rspec/mocks/method_double.rb

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,11 @@ def restore_original_method
8585
return unless @method_is_proxied
8686

8787
remove_method_from_definition_target
88-
@method_stasher.restore if @method_stasher.method_is_stashed?
89-
restore_original_visibility
88+
89+
if @method_stasher.method_is_stashed?
90+
@method_stasher.restore
91+
restore_original_visibility
92+
end
9093

9194
@method_is_proxied = false
9295
end
@@ -104,10 +107,7 @@ def show_frozen_warning
104107

105108
# @private
106109
def restore_original_visibility
107-
return unless @original_visibility &&
108-
MethodReference.method_defined_at_any_visibility?(object_singleton_class, @method_name)
109-
110-
object_singleton_class.__send__(@original_visibility, method_name)
110+
method_owner.__send__(@original_visibility, @method_name)
111111
end
112112

113113
# @private
@@ -249,6 +249,12 @@ def new_rspec_prepended_module
249249
end
250250
end
251251

252+
def method_owner
253+
@method_owner ||=
254+
# We do this because object.method might be overridden.
255+
::RSpec::Support.method_handle_for(object, @method_name).owner
256+
end
257+
252258
def remove_method_from_definition_target
253259
# In Ruby 2.4 and earlier, `remove_method` is private
254260
definition_target.__send__(:remove_method, @method_name)

spec/rspec/mocks/stub_spec.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ module ToBePrepended
138138
def value
139139
"#{super}_prepended".to_sym
140140
end
141+
142+
def value_without_super
143+
:prepended
144+
end
141145
end
142146

143147
it "handles stubbing prepended methods" do
@@ -165,6 +169,15 @@ def object.value; :original; end
165169
expect(object.value).to eq :stubbed
166170
end
167171

172+
it "handles stubbing prepending methods that were only defined on the prepended module" do
173+
object = Object.new
174+
object.singleton_class.send(:prepend, ToBePrepended)
175+
176+
expect(object.value_without_super).to eq :prepended
177+
allow(object).to receive(:value_without_super) { :stubbed }
178+
expect(object.value_without_super).to eq :stubbed
179+
end
180+
168181
it 'does not unnecessarily prepend a module when the prepended module does not override the stubbed method' do
169182
object = Object.new
170183
def object.value; :original; end
@@ -350,6 +363,21 @@ class << self; public :hello; end;
350363
expect(mod.hello).to eq(:hello)
351364
end
352365

366+
it "correctly restores from allow_any_instance_of for self extend" do
367+
mod = Module.new {
368+
extend self
369+
def hello; :hello; end
370+
}
371+
372+
allow_any_instance_of(mod).to receive(:hello) { :stub }
373+
374+
expect(mod.hello).to eq(:stub)
375+
376+
reset_all
377+
378+
expect(mod.hello).to eq(:hello)
379+
end
380+
353381
it "correctly handles stubbing inherited mixed in class methods" do
354382
mod = Module.new do
355383
def method_a

0 commit comments

Comments
 (0)