Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Already on GitHub? Sign in to your account

stubbing superclass class methods makes subclass class methods inaccessible #145

Closed
wants to merge 1 commit into
from
Jump to file or symbol
Failed to load files and symbols.
+63 −3
Split
View
@@ -496,6 +496,29 @@ def in_sequence(*sequences)
self
end
+ # Allows the expectation to match for subclasses
+ #
+ # Only applies to expectations on class methods.
+ #
+ # @param [value] to allow this expectation to match for subclasses
+ # @return [Expectation] the same expectation, thereby allowing invocations of other {Expectation} methods to be chained.
+ #
+ # @example
+ # class A
+ # def self.something
+ # 'A'
+ # end
+ # end
+ # class B < A
+ # end
+ #
+ # A.stubs(:something).including_subclass.returns('stub')
+ # B.something => "stub"
+ def including_subclasses(value = true)
+ @including_subclasses = value
+ self
+ end
+
# @private
attr_reader :backtrace
@@ -507,6 +530,7 @@ def initialize(mock, expected_method_name, backtrace = nil)
@ordering_constraints = []
@side_effects = []
@cardinality, @invocation_count = Cardinality.exactly(1), 0
+ @including_subclasses = false
@return_values = ReturnValues.new
@yield_parameters = YieldParameters.new
@backtrace = backtrace || caller
@@ -558,6 +582,11 @@ def satisfied?
end
# @private
+ def including_subclasses?
+ @including_subclasses
+ end
+
+ # @private
def invoke
@invocation_count += 1
perform_side_effects()
@@ -2,8 +2,9 @@ module Mocha
class ExpectationList
- def initialize
+ def initialize(klass = nil)
@expectations = []
+ @klass = klass
end
def add(expectation)
@@ -49,10 +50,25 @@ def any?
private
- def matching_expectations(method_name, *arguments)
+ def local_matching_expectations(method_name, *arguments)
@expectations.select { |e| e.match?(method_name, *arguments) }
end
+ def matching_expectations(method_name, *arguments)
+ expectations = local_matching_expectations(method_name, *arguments)
+ if @klass
+ klass = @klass
+ while klass
+ mocha = klass.mocha if klass.instance_variable_defined?(:@mocha)
+ klass = klass.superclass
+ next unless mocha
+ expectations += mocha.__expectations__.send(:local_matching_expectations, method_name, *arguments).
+ select { |expectation| expectation.including_subclasses? }
+ end
+ end
+ expectations
+ end
+
end
end
View
@@ -197,7 +197,9 @@ def responds_like_instance_of(responder_class)
def initialize(mockery, name = nil, &block)
@mockery = mockery
@name = name || DefaultName.new(self)
- @expectations = ExpectationList.new
+ klass = name.instance_variable_get(:@object) if name.is_a?(ImpersonatingName)
+ klass = nil unless klass.is_a?(Class)
+ @expectations = ExpectationList.new(klass)
@everything_stubbed = false
@responder = nil
instance_eval(&block) if block
@@ -72,4 +72,17 @@ def my_class_method
end
assert_equal :original_return_value, klass.send(:my_class_method)
end
+
+ def test_should_allow_including_subclasses_in_stub
+ superklass = Class.new do
+ class << self
+ def my_class_method
+ :original_return_value
+ end
+ end
+ end
+ klass = Class.new(superklass)
+ superklass.stubs(:my_class_method).including_subclasses.returns(:new_return_value)
+ assert_equal :new_return_value, klass.my_class_method
+ end
end