Skip to content

Commit

Permalink
Implicitly set responder on partial mocks
Browse files Browse the repository at this point in the history
We now automatically set a responder on mock object which are used for
partial mocks.

Having made the change above, I had to set include_all to true for the
call to Object#respond_to? in Mock#check_responder_responds_to in order
to fix a load of broken tests.

The legacy_behaviour_for_array_flatten condition in
Mock#check_responder_responds_to is needed to avoid a regression of #580
in Ruby < v2.3.

Hopefully this is a small step towards having
Configuration.prevent(:stubbing_non_existent_method) check Method#arity
and/or Method#parameters (#149) and rationalising
Configuration.stubbing_non_existent_method= & Mock#responds_like (#531).
  • Loading branch information
floehopper committed Dec 28, 2022
1 parent 089a688 commit de00251
Show file tree
Hide file tree
Showing 5 changed files with 19 additions and 13 deletions.
2 changes: 1 addition & 1 deletion lib/mocha/class_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def initialize(klass)

def mocha(instantiate = true)
if instantiate
@mocha ||= Mocha::Mockery.instance.mock_impersonating_any_instance_of(@stubba_object)
@mocha ||= Mocha::Mockery.instance.mock_impersonating_any_instance_of(@stubba_object).responds_like_instance_of(@stubba_object)
else
defined?(@mocha) ? @mocha : nil
end
Expand Down
11 changes: 8 additions & 3 deletions lib/mocha/mock.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
require 'mocha/parameters_matcher'
require 'mocha/argument_iterator'
require 'mocha/expectation_error_factory'
require 'mocha/ruby_version'

module Mocha
# Traditional mock object.
Expand Down Expand Up @@ -381,9 +382,13 @@ def raise_unexpected_invocation_error(invocation, matching_expectation)
end

def check_responder_responds_to(symbol)
if @responder && !@responder.respond_to?(symbol) # rubocop:disable Style/GuardClause
raise NoMethodError, "undefined method `#{symbol}' for #{mocha_inspect} which responds like #{@responder.mocha_inspect}"
end
return unless @responder

legacy_behaviour_for_array_flatten = !RUBY_V23_PLUS && !@responder.respond_to?(symbol) && (symbol == :to_ary)

return if @responder.respond_to?(symbol, true) && !legacy_behaviour_for_array_flatten

raise NoMethodError, "undefined method `#{symbol}' for #{mocha_inspect} which responds like #{@responder.mocha_inspect}"
end

def check_expiry
Expand Down
2 changes: 1 addition & 1 deletion lib/mocha/object_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ module ObjectMethods
# @private
def mocha(instantiate = true)
if instantiate
@mocha ||= Mocha::Mockery.instance.mock_impersonating(self)
@mocha ||= Mocha::Mockery.instance.mock_impersonating(self).responds_like(self)
else
defined?(@mocha) ? @mocha : nil
end
Expand Down
1 change: 1 addition & 0 deletions lib/mocha/ruby_version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
module Mocha
RUBY_V23_PLUS = Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.3')
RUBY_V27_PLUS = Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('2.7')
end
16 changes: 8 additions & 8 deletions test/acceptance/responds_like_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,14 +131,14 @@ def foo; end
assert_passed(test_result)
end

def test_mock_which_responds_like_object_with_protected_method_raises_no_method_error_when_method_is_not_stubbed
def test_mock_which_responds_like_object_with_protected_method_raises_unexpected_invocation_exception_when_method_is_not_stubbed
object = Class.new do
def foo; end
protected :foo
end.new
test_result = run_as_test do
m = mock.responds_like(object)
assert_raises(NoMethodError) { m.foo } # vs Minitest::Assertion for public method
assert_raises(Minitest::Assertion) { m.foo }
end
assert_passed(test_result)
end
Expand Down Expand Up @@ -169,15 +169,15 @@ def foo; end
assert_passed(test_result)
end

def test_mock_which_responds_like_object_with_protected_method_raises_no_method_error_when_method_is_stubbed
def test_mock_which_responds_like_object_with_protected_method_does_not_raise_exception_when_method_is_stubbed
object = Class.new do
def foo; end
protected :foo
end.new
test_result = run_as_test do
m = mock.responds_like(object)
m.stubs(:foo)
assert_raises(NoMethodError) { m.foo } # vs no exception for public method
assert_nil m.foo
end
assert_passed(test_result)
end
Expand All @@ -197,14 +197,14 @@ def foo; end
assert_passed(test_result)
end

def test_mock_which_responds_like_object_with_private_method_raises_no_method_error_when_method_is_not_stubbed
def test_mock_which_responds_like_object_with_private_method_raises_unexpected_invocation_exception_when_method_is_not_stubbed
object = Class.new do
def foo; end
private :foo
end.new
test_result = run_as_test do
m = mock.responds_like(object)
assert_raises(NoMethodError) { m.foo } # vs Minitest::Assertion for public method
assert_raises(Minitest::Assertion) { m.foo }
end
assert_passed(test_result)
end
Expand Down Expand Up @@ -235,15 +235,15 @@ def foo; end
assert_passed(test_result)
end

def test_mock_which_responds_like_object_with_private_method_raises_no_method_error_when_method_is_stubbed
def test_mock_which_responds_like_object_with_private_method_does_not_raise_exception_when_method_is_stubbed
object = Class.new do
def foo; end
private :foo
end.new
test_result = run_as_test do
m = mock.responds_like(object)
m.stubs(:foo)
assert_raises(NoMethodError) { m.foo } # vs no exception for public method
assert_nil m.foo
end
assert_passed(test_result)
end
Expand Down

0 comments on commit de00251

Please sign in to comment.