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

Commit 6b87c96

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 bbb50c9 commit 6b87c96

File tree

3 files changed

+26
-7
lines changed

3 files changed

+26
-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: 10 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,10 @@ def definition_target
261261

262262
private
263263

264+
def method_owner
265+
Object.instance_method(:method).bind(object).call(@method_name).owner
266+
end
267+
264268
def remove_method_from_definition_target
265269
definition_target.__send__(:remove_method, @method_name)
266270
rescue NameError

spec/rspec/mocks/stub_spec.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ module ToBePrepended
127127
def value
128128
"#{super}_prepended".to_sym
129129
end
130+
131+
def value_without_super
132+
:prepended
133+
end
130134
end
131135

132136
it "handles stubbing prepended methods" do
@@ -154,6 +158,15 @@ def object.value; :original; end
154158
expect(object.value).to eq :stubbed
155159
end
156160

161+
it "handles stubbing prepending methods that were only defined on the prepended module" do
162+
object = Object.new
163+
object.singleton_class.send(:prepend, ToBePrepended)
164+
165+
expect(object.value_without_super).to eq :prepended
166+
allow(object).to receive(:value_without_super) { :stubbed }
167+
expect(object.value_without_super).to eq :stubbed
168+
end
169+
157170
it 'does not unnecessarily prepend a module when the prepended module does not override the stubbed method' do
158171
object = Object.new
159172
def object.value; :original; end

0 commit comments

Comments
 (0)