Skip to content

Commit

Permalink
Fix stubbing prepended only methods
Browse files Browse the repository at this point in the history
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
  • Loading branch information
godfat committed Oct 7, 2020
1 parent c1a86de commit c364a0a
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 7 deletions.
4 changes: 3 additions & 1 deletion lib/rspec/mocks/instance_method_stasher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,9 @@ def method_owned_by_klass?
# The owner of M.b is the raw Module object, instead of the expected
# singleton class of the module
return true if RUBY_VERSION < '1.9' && owner == @object
owner == @klass || !(method_defined_on_klass?(owner))
owner == @klass ||
owner.singleton_class == @klass || # When `extend self` is used
!(method_defined_on_klass?(owner))
end
end
end
Expand Down
16 changes: 10 additions & 6 deletions lib/rspec/mocks/method_double.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,11 @@ def restore_original_method
return unless @method_is_proxied

remove_method_from_definition_target
@method_stasher.restore if @method_stasher.method_is_stashed?
restore_original_visibility

if @method_stasher.method_is_stashed?
@method_stasher.restore
restore_original_visibility
end

@method_is_proxied = false
end
Expand All @@ -102,10 +105,7 @@ def show_frozen_warning

# @private
def restore_original_visibility
return unless @original_visibility &&
MethodReference.method_defined_at_any_visibility?(object_singleton_class, @method_name)

object_singleton_class.__send__(@original_visibility, method_name)
method_owner.__send__(@original_visibility, @method_name)
end

# @private
Expand Down Expand Up @@ -261,6 +261,10 @@ def definition_target

private

def method_owner
Object.instance_method(:method).bind(object).call(@method_name).owner
end

def remove_method_from_definition_target
definition_target.__send__(:remove_method, @method_name)
rescue NameError
Expand Down
13 changes: 13 additions & 0 deletions spec/rspec/mocks/stub_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ module ToBePrepended
def value
"#{super}_prepended".to_sym
end

def value_without_super
:prepended
end
end

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

it "handles stubbing prepending methods that were only defined on the prepended module" do
object = Object.new
object.singleton_class.send(:prepend, ToBePrepended)

expect(object.value_without_super).to eq :prepended
allow(object).to receive(:value_without_super) { :stubbed }
expect(object.value_without_super).to eq :stubbed
end

it 'does not unnecessarily prepend a module when the prepended module does not override the stubbed method' do
object = Object.new
def object.value; :original; end
Expand Down

0 comments on commit c364a0a

Please sign in to comment.