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

Commit b50456c

Browse files
committed
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 c1a86de commit b50456c

File tree

3 files changed

+31
-7
lines changed

3 files changed

+31
-7
lines changed

lib/rspec/mocks/instance_method_stasher.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,9 @@ def method_owned_by_klass?
139139
# The owner of M.b is the raw Module object, instead of the expected
140140
# singleton class of the module
141141
return true if RUBY_VERSION < '1.9' && owner == @object
142-
owner == @klass || !(method_defined_on_klass?(owner))
142+
owner == @klass ||
143+
owner.singleton_class == @klass || # When `extend self` is used
144+
!(method_defined_on_klass?(owner))
143145
end
144146
end
145147
end

lib/rspec/mocks/method_double.rb

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,11 @@ def restore_original_method
8383
return unless @method_is_proxied
8484

8585
remove_method_from_definition_target
86-
@method_stasher.restore if @method_stasher.method_is_stashed?
87-
restore_original_visibility
86+
87+
if @method_stasher.method_is_stashed?
88+
@method_stasher.restore
89+
restore_original_visibility
90+
end
8891

8992
@method_is_proxied = false
9093
end
@@ -102,10 +105,7 @@ def show_frozen_warning
102105

103106
# @private
104107
def restore_original_visibility
105-
return unless @original_visibility &&
106-
MethodReference.method_defined_at_any_visibility?(object_singleton_class, @method_name)
107-
108-
object_singleton_class.__send__(@original_visibility, method_name)
108+
method_owner.__send__(@original_visibility, @method_name)
109109
end
110110

111111
# @private
@@ -261,6 +261,15 @@ def definition_target
261261

262262
private
263263

264+
def method_owner
265+
@method_owner ||=
266+
if object.kind_of?(Object)
267+
Object.instance_method(:method).bind(object).call(@method_name).owner
268+
else
269+
object.method(@method_name).owner
270+
end
271+
end
272+
264273
def remove_method_from_definition_target
265274
definition_target.__send__(:remove_method, @method_name)
266275
rescue NameError

spec/rspec/mocks/stub_spec.rb

Lines changed: 13 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

0 commit comments

Comments
 (0)