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

Commit c84498e

Browse files
authored
Merge pull request #1218 from godfat/fix-stubbing-prepended-only-methods
Fix stubbing prepended only methods
2 parents a692dd4 + 604dc95 commit c84498e

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)