class Mocha::Mock
explicitly define a total or partial ordering of invocations.
different mock objects, use the {Expectation#in_sequence} method to
If you want to specify more complex ordering or order invocations across
object.expected_method # => raises exception of class Exception1
object.expected_method # => 2
object.expected_method # => 1
object.stubs(:expected_method).returns(1, 2).then.raises(Exception)
object = mock()
@example
{Expectation#then} if desired).
{Expectation#returns} and {Expectation#raises} (along with syntactic sugar
actions for a method. You can also chain multiple calls to
{Expectation#returns} method with multiple arguments to create multiple
same method with exactly the same matchers. Instead, use the
The best thing to do is not set up multiple expectations and stubs for the
rather than the same order.
invoked in the opposite order than that in which they were specified,
- if you create different expectations for the same method, they will be
instead, possibly masking test failures.
expectation will match, and when it stops matching the stub will be used
- if you create a stub and then an expectation for the same method, the
be met.
stub will always override the expectation and the expectation will never
- if you create an expectation and then a stub for the same method, the
However, there are some possible “gotchas” caused by this scheme:
described below.
{Expectation#returns} method with multiple arguments to do this, as
action per invocation. However, it’s better to use the
- Set up different once expectations for the same method with different
override some of those stubs in individual tests.
- Set up default stubs in your the setup method of your test class and
This scheme allows you to:
against invocations.
while an +expects(:foo).at_least_once+ expectation will always be matched
expectation only matches once and will be ignored on future invocations
matching further invocations. For example, an +expects(:foo).once+
invocation. After the invocation, the matching expectation might stop
its expectations from newest to oldest to find one that matches the
When a method is invoked on a mock object, the mock object searches through
sugar to make the intent of the test more explicit.
expectation of zero or more invocations. The {#stubs} method is syntactic
Stubs and expectations are basically the same thing. A stub is just anNoMethodError upon invocation otherwise.
will actually respond to the methods being stubbed, throwing a
making it a safer verifying mock. They check that the underlying responder
They force the mock to indicate what it is supposed to be mocking, thus
can therefore, be chained to the original creation methods in {API}.
{responds_like} and {responds_like_instance_of} both return a {Mock}, and
by methods on {Expectation}.
{expects} and {stubs} return an {Expectation} which can be further modified
Traditional mock object.
def __expectations__
- Private:   -   
 
def __expectations__ @expectations end
def __expire__(origin)
- Private:   -   
 
def __expire__(origin) @expired = origin || true end
def __verified__?(assertion_counter = nil)
- Private:   -   
 
def __verified__?(assertion_counter = nil) @expectations.verified?(assertion_counter) end
def all_expectations
- Private:   -   
 
def all_expectations @receiver.mocks.inject(ExpectationList.new) { |e, m| e + m.__expectations__ } end
def any_expectations?
- Private:   -   
 
def any_expectations? @expectations.any? end
def check_expiry
def check_expiry return unless @expired origin = @expired == true ? 'one test' : @expired sentences = [ "#{mocha_inspect} was instantiated in #{origin} but it is receiving invocations within another test.", 'This can lead to unintended interactions between tests and hence unexpected test failures.', 'Ensure that every test correctly cleans up any state that it introduces.' ] raise StubbingError.new(sentences.join(' '), caller) end
def check_responder_responds_to(symbol)
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 end
def ensure_method_not_already_defined(method_name)
- Private:   -   
 
def ensure_method_not_already_defined(method_name) __singleton_class__.send(:undef_method, method_name) if __singleton_class__.method_defined?(method_name) || __singleton_class__.private_method_defined?(method_name) end
def expects(method_name_or_hash, backtrace = nil)
- Example:  Setup multiple expectations using +expected_methods_vs_return_values+. -   
 
Example: Expected method invoked twice so error raised -
Example: Expected method not invoked so error raised -
Example: Expected method invoked once so no error raised -
Parameters:
- 
        
expected_methods_vs_return_values(Hash) -- expected method name symbols as keys and corresponding return values as values - these expectations are setup as if {#expects} were called multiple times. - 
        
method_name(Symbol, String) -- name of expected method 
Overloads:
- 
        
def expects(expected_methods_vs_return_values) - 
        
def expects(method_name) 
Returns:
- 
        
(Expectation)- last-built expectation which can be further modified by methods on {Expectation}. 
def expects(method_name_or_hash, backtrace = nil) expectation = nil iterator = ArgumentIterator.new(method_name_or_hash) iterator.each do |*args| method_name = args.shift ensure_method_not_already_defined(method_name) expectation = Expectation.new(self, method_name, backtrace) expectation.in_sequence(@mockery.sequences.last) if @mockery.sequences.any? expectation.returns(args.shift) unless args.empty? @expectations.add(expectation) end expectation end
def handle_method_call(symbol, arguments, block)
- Private:   -   
 
def handle_method_call(symbol, arguments, block) check_expiry check_responder_responds_to(symbol) invocation = Invocation.new(self, symbol, arguments, block) if (matching_expectation_allowing_invocation = all_expectations.match_allowing_invocation(invocation)) matching_expectation_allowing_invocation.invoke(invocation) elsif (matching_expectation = all_expectations.match(invocation, ignoring_order: true)) || (!matching_expectation && !@everything_stubbed) raise_unexpected_invocation_error(invocation, matching_expectation) end end
def initialize(mockery, name = nil, receiver = nil)
- Private:   -   
 
def initialize(mockery, name = nil, receiver = nil) @mockery = mockery @name = name || DefaultName.new(self) @receiver = receiver || DefaultReceiver.new(self) @expectations = ExpectationList.new @everything_stubbed = false @responder = nil @unexpected_invocation = nil @expired = false end
def inspect
- Private:   -   
 
def inspect mocha_inspect end
def method_missing(symbol, *arguments, &block) # rubocop:disable Style/MethodMissingSuper
- Private:   -   
 
def method_missing(symbol, *arguments, &block) # rubocop:disable Style/MethodMissingSuper handle_method_call(symbol, arguments, block) end
def mocha_inspect
- Private:   -   
 
def mocha_inspect @name.mocha_inspect end
def raise_unexpected_invocation_error(invocation, matching_expectation)
def raise_unexpected_invocation_error(invocation, matching_expectation) if @unexpected_invocation.nil? @unexpected_invocation = invocation matching_expectation.invoke(invocation) if matching_expectation call_description = @unexpected_invocation.call_description if matching_expectation && !matching_expectation.in_correct_order? call_description += ' invoked out of order' end message = "#{call_description}\n#{@mockery.mocha_inspect}" else message = @unexpected_invocation.short_call_description end raise ExpectationErrorFactory.build("unexpected invocation: #{message}", caller) end
def respond_to_missing?(symbol, _include_all)
- Private:   -   
 
def respond_to_missing?(symbol, _include_all) if @responder @responder.respond_to?(symbol) else @everything_stubbed || all_expectations.matches_method?(symbol) end end
def responds_like(responder)
- Example:  Using {#responds_like} with a class method -   
 
Example: Using {#responds_like} with an instance method -
Example: Normal mocking -
Other tags:
- See:  #responds_like_instance_of -   
 
Returns:
- 
        
(Mock)- the same {Mock} instance, thereby allowing invocations of other {Mock} methods to be chained. 
Parameters:
- 
        
responder(Object, #respond_to?) -- an object used to determine whether {Mock} instance should +#respond_to?+ to an invocation. 
def responds_like(responder) @responder = responder self end
def responds_like_instance_of(responder_class)
- Example:  Using {#responds_like_instance_of} -   
 
Other tags:
- See:  #responds_like -   
 
Returns:
- 
        
(Mock)- the same {Mock} instance, thereby allowing invocations of other {Mock} methods to be chained. 
Parameters:
- 
        
responder_class(Class) -- a class used to determine whether {Mock} instance should +#respond_to?+ to an invocation. 
def responds_like_instance_of(responder_class) responds_like(responder_class.allocate) end
def stub_everything
- Private:   -   
 
def stub_everything @everything_stubbed = true end
def stubs(method_name_or_hash, backtrace = nil)
- Example:  Setup multiple expectations using +stubbed_methods_vs_return_values+. -   
 
Example: No error raised however many times stubbed method is invoked -
Parameters:
- 
        
stubbed_methods_vs_return_values(Hash) -- stubbed method name symbols as keys and corresponding return values as values - these stubbed methods are setup as if {#stubs} were called multiple times. - 
        
method_name(Symbol, String) -- name of stubbed method 
Overloads:
- 
        
def stubs(stubbed_methods_vs_return_values) - 
        
def stubs(method_name) 
Returns:
- 
        
(Expectation)- last-built expectation which can be further modified by methods on {Expectation}. 
def stubs(method_name_or_hash, backtrace = nil) expectation = nil iterator = ArgumentIterator.new(method_name_or_hash) iterator.each do |*args| method_name = args.shift ensure_method_not_already_defined(method_name) expectation = Expectation.new(self, method_name, backtrace) expectation.at_least(0) expectation.in_sequence(@mockery.sequences.last) if @mockery.sequences.any? expectation.returns(args.shift) unless args.empty? @expectations.add(expectation) end expectation end
def unstub(*method_names)
- Example:  Unstubbing multiple methods. -   
 
Example: Invoking an unstubbed method causes error to be raised -
Parameters:
- 
        
method_names(Array) -- names of methods to unstub. 
def unstub(*method_names) method_names.each do |method_name| @expectations.remove_all_matching_method(method_name) end end