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 Apr 10, 2018
1 parent bbb50c9 commit 938dcbf
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 5 deletions.
21 changes: 16 additions & 5 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,12 @@ def show_frozen_warning

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

return unless method_owner && @original_visibility &&
MethodReference.method_defined_at_any_visibility?(method_owner, @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 +266,12 @@ def definition_target

private

def find_method_owner
if Object.instance_method(:respond_to?).bind(object).call(@method_name, true)
Object.instance_method(:method).bind(object).call(@method_name).owner
end
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 @@ -127,6 +127,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 @@ -154,6 +158,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 938dcbf

Please sign in to comment.