require 'mocha/expectation'
require 'mocha/expectation_list'
require 'mocha/metaclass'
require 'mocha/names'
require 'mocha/method_matcher'
require 'mocha/parameters_matcher'
require 'mocha/unexpected_invocation'
require 'mocha/argument_iterator'
require 'mocha/mockery'
module Mocha # :nodoc:
# Traditional mock object.
#
# Methods return an Expectation which can be further modified by methods on Expectation.
class Mock
# :call-seq: expects(method_name) -> expectation
# expects(method_names_vs_return_values) -> last expectation
#
# Adds an expectation that a method identified by +method_name+ Symbol/String must be called exactly once with any parameters.
# Returns the new expectation which can be further modified by methods on Expectation.
# object = mock()
# object.expects(:method1)
# object.method1
# # no error raised
#
# object = mock()
# object.expects(:method1)
# # error raised, because method1 not called exactly once
# If +method_names_vs_return_values+ is a +Hash+, an expectation will be set up for each entry using the key as +method_name+ and value as +return_value+.
# object = mock()
# object.expects(:method1 => :result1, :method2 => :result2)
#
# # exactly equivalent to
#
# object = mock()
# object.expects(:method1).returns(:result1)
# object.expects(:method2).returns(:result2)
#
# Aliased by \_\_expects\_\_
def expects(method_name_or_hash, backtrace = nil)
iterator = ArgumentIterator.new(method_name_or_hash)
iterator.each { |*args|
method_name = args.shift
ensure_method_not_already_defined(method_name)
expectation = Expectation.new(self, method_name, backtrace)
expectation.returns(args.shift) if args.length > 0
@expectations.add(expectation)
}
end
# :call-seq: stubs(method_name) -> expectation
# stubs(method_names_vs_return_values) -> last expectation
#
# Adds an expectation that a method identified by +method_name+ Symbol/String may be called any number of times with any parameters.
# Returns the new expectation which can be further modified by methods on Expectation.
# object = mock()
# object.stubs(:method1)
# object.method1
# object.method1
# # no error raised
# If +method_names_vs_return_values+ is a +Hash+, an expectation will be set up for each entry using the key as +method_name+ and value as +return_value+.
# object = mock()
# object.stubs(:method1 => :result1, :method2 => :result2)
#
# # exactly equivalent to
#
# object = mock()
# object.stubs(:method1).returns(:result1)
# object.stubs(:method2).returns(:result2)
#
# Aliased by \_\_stubs\_\_
def stubs(method_name_or_hash, backtrace = nil)
iterator = ArgumentIterator.new(method_name_or_hash)
iterator.each { |*args|
method_name = args.shift
ensure_method_not_already_defined(method_name)
expectation = Expectation.new(self, method_name, backtrace)
expectation.at_least(0)
expectation.returns(args.shift) if args.length > 0
@expectations.add(expectation)
}
end
# :call-seq: responds_like(responder) -> mock
#
# Constrains the +mock+ so that it can only expect or stub methods to which +responder+ responds. The constraint is only applied at method invocation time.
#
# A +NoMethodError+ will be raised if the +responder+ does not respond_to? a method invocation (even if the method has been expected or stubbed).
#
# The +mock+ will delegate its respond_to? method to the +responder+.
# class Sheep
# def chew(grass); end
# def self.number_of_legs; end
# end
#
# sheep = mock('sheep')
# sheep.expects(:chew)
# sheep.expects(:foo)
# sheep.respond_to?(:chew) # => true
# sheep.respond_to?(:foo) # => true
# sheep.chew
# sheep.foo
# # no error raised
#
# sheep = mock('sheep')
# sheep.responds_like(Sheep.new)
# sheep.expects(:chew)
# sheep.expects(:foo)
# sheep.respond_to?(:chew) # => true
# sheep.respond_to?(:foo) # => false
# sheep.chew
# sheep.foo # => raises NoMethodError exception
#
# sheep_class = mock('sheep_class')
# sheep_class.responds_like(Sheep)
# sheep_class.stubs(:number_of_legs).returns(4)
# sheep_class.expects(:foo)
# sheep_class.respond_to?(:number_of_legs) # => true
# sheep_class.respond_to?(:foo) # => false
# assert_equal 4, sheep_class.number_of_legs
# sheep_class.foo # => raises NoMethodError exception
#
# Aliased by +quacks_like+
def responds_like(object)
@responder = object
self
end
# :stopdoc:
def initialize(name = nil, &block)
@name = name || DefaultName.new(self)
@expectations = ExpectationList.new
@everything_stubbed = false
@responder = nil
instance_eval(&block) if block
end
attr_reader :everything_stubbed, :expectations
alias_method :__expects__, :expects
alias_method :__stubs__, :stubs
alias_method :quacks_like, :responds_like
def stub_everything
@everything_stubbed = true
end
def method_missing(symbol, *arguments, &block)
if @responder and not @responder.respond_to?(symbol)
raise NoMethodError, "undefined method `#{symbol}' for #{self.mocha_inspect} which responds like #{@responder.mocha_inspect}"
end
if matching_expectation_allowing_invocation = @expectations.match_allowing_invocation(symbol, *arguments)
matching_expectation_allowing_invocation.invoke(&block)
else
if (matching_expectation = @expectations.match(symbol, *arguments)) || (!matching_expectation && !@everything_stubbed)
matching_expectation.invoke(&block) if matching_expectation
message = UnexpectedInvocation.new(self, symbol, *arguments).to_s
message << Mockery.instance.mocha_inspect
raise ExpectationError.new(message, caller)
end
end
end
def respond_to?(symbol, include_private = false)
if @responder then
if @responder.method(:respond_to?).arity > 1
@responder.respond_to?(symbol, include_private)
else
@responder.respond_to?(symbol)
end
else
@everything_stubbed || @expectations.matches_method?(symbol)
end
end
def __verified__?(assertion_counter = nil)
@expectations.verified?(assertion_counter)
end
def mocha_inspect
@name.mocha_inspect
end
def inspect
mocha_inspect
end
def ensure_method_not_already_defined(method_name)
self.__metaclass__.send(:undef_method, method_name) if self.__metaclass__.method_defined?(method_name)
end
# :startdoc:
end
end