diff options
| author | (no author) <(no author)@980ebf18-57e1-0310-9a29-db15c13687c0> | 2007-08-09 08:41:54 +0000 |
|---|---|---|
| committer | (no author) <(no author)@980ebf18-57e1-0310-9a29-db15c13687c0> | 2007-08-09 08:41:54 +0000 |
| commit | 5a25701723431e0ebe2d7134ab65d56bee2c5244 (patch) | |
| tree | 248522b40031089a779760d41003fc23e00f916b | |
| parent | 5e8d71da3e060036c695a2db904c880567fc9f69 (diff) | |
| download | puppet-5a25701723431e0ebe2d7134ab65d56bee2c5244.tar.gz puppet-5a25701723431e0ebe2d7134ab65d56bee2c5244.tar.xz puppet-5a25701723431e0ebe2d7134ab65d56bee2c5244.zip | |
Upgrade mocha to 0.5.1, which gives much better error messages
git-svn-id: https://reductivelabs.com/svn/puppet/trunk@2758 980ebf18-57e1-0310-9a29-db15c13687c0
28 files changed, 892 insertions, 228 deletions
diff --git a/test/lib/mocha/auto_verify.rb b/test/lib/mocha/auto_verify.rb index 7aa5e8b30..dce877bde 100644 --- a/test/lib/mocha/auto_verify.rb +++ b/test/lib/mocha/auto_verify.rb @@ -1,12 +1,12 @@ require 'mocha/mock' -# Methods added to TestCase allowing creation of mock objects. -# -# Mocks created this way will have their expectations automatically verified at the end of the test. -# -# See Mocha::MockMethods for methods on mock objects. -module Mocha +module Mocha # :nodoc: + # Methods added to TestCase allowing creation of traditional mock objects. + # + # Mocks created this way will have their expectations automatically verified at the end of the test. + # + # See Mock for methods on mock objects. module AutoVerify def mocks # :nodoc: diff --git a/test/lib/mocha/deprecation.rb b/test/lib/mocha/deprecation.rb new file mode 100644 index 000000000..7448510ec --- /dev/null +++ b/test/lib/mocha/deprecation.rb @@ -0,0 +1,22 @@ +module Mocha + + class Deprecation + + class << self + + attr_accessor :mode, :messages + + def warning(message) + @messages << message + $stderr.puts "Mocha deprecation warning: #{message}" unless mode == :disabled + $stderr.puts caller.join("\n ") if mode == :debug + end + + end + + self.mode = :enabled + self.messages = [] + + end + +end
\ No newline at end of file diff --git a/test/lib/mocha/exception_raiser.rb b/test/lib/mocha/exception_raiser.rb new file mode 100644 index 000000000..266e209a2 --- /dev/null +++ b/test/lib/mocha/exception_raiser.rb @@ -0,0 +1,17 @@ +module Mocha # :nodoc: + + class ExceptionRaiser # :nodoc: + + def initialize(exception, message) + @exception, @message = exception, message + end + + def evaluate + raise @exception, @exception.to_s if @exception == Interrupt + raise @exception, @message if @message + raise @exception + end + + end + +end diff --git a/test/lib/mocha/expectation.rb b/test/lib/mocha/expectation.rb index 0073c1e13..49b39bea9 100644 --- a/test/lib/mocha/expectation.rb +++ b/test/lib/mocha/expectation.rb @@ -1,21 +1,18 @@ require 'mocha/infinite_range' require 'mocha/pretty_parameters' require 'mocha/expectation_error' +require 'mocha/return_values' +require 'mocha/exception_raiser' +require 'mocha/yield_parameters' +require 'mocha/is_a' -class Object - - alias_method :__is_a__, :is_a? - -end - -module Mocha - # Methods on expectations returned from Mocha::MockMethods#expects and Mocha::MockMethods#stubs +module Mocha # :nodoc: + + # Methods on expectations returned from Mock#expects, Mock#stubs, Object#expects and Object#stubs. class Expectation # :stopdoc: - class InvalidExpectation < Exception; end - class AlwaysEqual def ==(other) true @@ -26,23 +23,26 @@ module Mocha def initialize(mock, method_name, backtrace = nil) @mock, @method_name = mock, method_name - @count = 1 + @expected_count = 1 @parameters, @parameter_block = AlwaysEqual.new, nil - @invoked, @return_value = 0, nil + @invoked_count, @return_values = 0, ReturnValues.new @backtrace = backtrace || caller - @yield = nil + @yield_parameters = YieldParameters.new end - def yield? - @yield - end - def match?(method_name, *arguments) + return false unless @method_name == method_name if @parameter_block then - @parameter_block.call(*arguments) + return false unless @parameter_block.call(*arguments) + else + return false unless (@parameters == arguments) + end + if @expected_count.is_a?(Range) then + return false unless @invoked_count < @expected_count.last else - (@method_name == method_name) and (@parameters == arguments) + return false unless @invoked_count < @expected_count end + return true end # :startdoc: @@ -54,34 +54,63 @@ module Mocha # +range+ can be specified as an exact integer or as a range of integers # object = mock() # object.expects(:expected_method).times(3) - # 3.times { object.expected_method } # => verify succeeds + # 3.times { object.expected_method } + # # => verify succeeds # # object = mock() # object.expects(:expected_method).times(3) - # 2.times { object.expected_method } # => verify fails + # 2.times { object.expected_method } + # # => verify fails # # object = mock() # object.expects(:expected_method).times(2..4) - # 3.times { object.expected_method } # => verify succeeds + # 3.times { object.expected_method } + # # => verify succeeds # # object = mock() # object.expects(:expected_method).times(2..4) - # object.expected_method # => verify fails + # object.expected_method + # # => verify fails def times(range) - @count = range + @expected_count = range + self + end + + # :call-seq: once() -> expectation + # + # Modifies expectation so that the expected method must be called exactly once. + # Note that this is the default behaviour for an expectation, but you may wish to use it for clarity/emphasis. + # object = mock() + # object.expects(:expected_method).once + # object.expected_method + # # => verify succeeds + # + # object = mock() + # object.expects(:expected_method).once + # object.expected_method + # object.expected_method + # # => verify fails + # + # object = mock() + # object.expects(:expected_method).once + # # => verify fails + def once() + times(1) self end - # :call-seq: never -> expectation + # :call-seq: never() -> expectation # # Modifies expectation so that the expected method must never be called. # object = mock() # object.expects(:expected_method).never - # object.expected_method # => verify fails + # object.expected_method + # # => verify fails # # object = mock() # object.expects(:expected_method).never - # object.expected_method # => verify succeeds + # object.expected_method + # # => verify succeeds def never times(0) self @@ -92,11 +121,13 @@ module Mocha # Modifies expectation so that the expected method must be called at least a +minimum_number_of_times+. # object = mock() # object.expects(:expected_method).at_least(2) - # 3.times { object.expected_method } # => verify succeeds + # 3.times { object.expected_method } + # # => verify succeeds # # object = mock() # object.expects(:expected_method).at_least(2) - # object.expected_method # => verify fails + # object.expected_method + # # => verify fails def at_least(minimum_number_of_times) times(Range.at_least(minimum_number_of_times)) self @@ -107,7 +138,8 @@ module Mocha # Modifies expectation so that the expected method must be called at least once. # object = mock() # object.expects(:expected_method).at_least_once - # object.expected_method # => verify succeeds + # object.expected_method + # # => verify succeeds # # object = mock() # object.expects(:expected_method).at_least_once @@ -122,11 +154,13 @@ module Mocha # Modifies expectation so that the expected method must be called at most a +maximum_number_of_times+. # object = mock() # object.expects(:expected_method).at_most(2) - # 2.times { object.expected_method } # => verify succeeds + # 2.times { object.expected_method } + # # => verify succeeds # # object = mock() # object.expects(:expected_method).at_most(2) - # 3.times { object.expected_method } # => verify fails + # 3.times { object.expected_method } + # # => verify fails def at_most(maximum_number_of_times) times(Range.at_most(maximum_number_of_times)) self @@ -137,11 +171,13 @@ module Mocha # Modifies expectation so that the expected method must be called at most once. # object = mock() # object.expects(:expected_method).at_most_once - # object.expected_method # => verify succeeds + # object.expected_method + # # => verify succeeds # # object = mock() # object.expects(:expected_method).at_most_once - # 2.times { object.expected_method } # => verify fails + # 2.times { object.expected_method } + # # => verify fails def at_most_once() at_most(1) self @@ -152,20 +188,26 @@ module Mocha # Modifies expectation so that the expected method must be called with specified +arguments+. # object = mock() # object.expects(:expected_method).with(:param1, :param2) - # object.expected_method(:param1, :param2) # => verify succeeds + # object.expected_method(:param1, :param2) + # # => verify succeeds # # object = mock() # object.expects(:expected_method).with(:param1, :param2) - # object.expected_method(:param3) # => verify fails + # object.expected_method(:param3) + # # => verify fails + # May be used with parameter matchers in Mocha::ParameterMatchers. + # # If a +parameter_block+ is given, the block is called with the parameters passed to the expected method. # The expectation is matched if the block evaluates to +true+. # object = mock() # object.expects(:expected_method).with() { |value| value % 4 == 0 } - # object.expected_method(16) # => verify succeeds + # object.expected_method(16) + # # => verify succeeds # # object = mock() # object.expects(:expected_method).with() { |value| value % 4 == 0 } - # object.expected_method(17) # => verify fails + # object.expected_method(17) + # # => verify fails def with(*arguments, ¶meter_block) @parameters, @parameter_block = arguments, parameter_block class << @parameters; def to_s; join(', '); end; end @@ -180,12 +222,42 @@ module Mocha # yielded_value = nil # object.expected_method { |value| yielded_value = value } # yielded_value # => 'result' + # May be called multiple times on the same expectation for consecutive invocations. Also see Expectation#then. + # object = mock() + # object.stubs(:expected_method).yields(1).then.yields(2) + # yielded_values_from_first_invocation = [] + # yielded_values_from_second_invocation = [] + # object.expected_method { |value| yielded_values_from_first_invocation << value } # first invocation + # object.expected_method { |value| yielded_values_from_second_invocation << value } # second invocation + # yielded_values_from_first_invocation # => [1] + # yielded_values_from_second_invocation # => [2] def yields(*parameters) - @yield = true - @parameters_to_yield = parameters + @yield_parameters.add(*parameters) self end - + + # :call-seq: multiple_yields(*parameter_groups) -> expectation + # + # Modifies expectation so that when the expected method is called, it yields multiple times per invocation with the specified +parameter_groups+. + # object = mock() + # object.expects(:expected_method).multiple_yields(['result_1', 'result_2'], ['result_3']) + # yielded_values = [] + # object.expected_method { |*values| yielded_values << values } + # yielded_values # => [['result_1', 'result_2'], ['result_3]] + # May be called multiple times on the same expectation for consecutive invocations. Also see Expectation#then. + # object = mock() + # object.stubs(:expected_method).multiple_yields([1, 2], [3]).then.multiple_yields([4], [5, 6]) + # yielded_values_from_first_invocation = [] + # yielded_values_from_second_invocation = [] + # object.expected_method { |*values| yielded_values_from_first_invocation << values } # first invocation + # object.expected_method { |*values| yielded_values_from_second_invocation << values } # second invocation + # yielded_values_from_first_invocation # => [[1, 2], [3]] + # yielded_values_from_second_invocation # => [[4], [5, 6]] + def multiple_yields(*parameter_groups) + @yield_parameters.multiple_add(*parameter_groups) + self + end + # :call-seq: returns(value) -> expectation # :call-seq: returns(*values) -> expectation # @@ -199,13 +271,30 @@ module Mocha # object.stubs(:stubbed_method).returns(1, 2) # object.stubbed_method # => 1 # object.stubbed_method # => 2 - # If +value+ is a Proc, then expected method will return result of calling Proc. + # May be called multiple times on the same expectation. Also see Expectation#then. + # object = mock() + # object.stubs(:expected_method).returns(1, 2).then.returns(3) + # object.expected_method # => 1 + # object.expected_method # => 2 + # object.expected_method # => 3 + # May be called in conjunction with Expectation#raises on the same expectation. + # object = mock() + # object.stubs(:expected_method).returns(1, 2).then.raises(Exception) + # object.expected_method # => 1 + # object.expected_method # => 2 + # object.expected_method # => raises exception of class Exception1 + # If +value+ is a +Proc+, then the expected method will return the result of calling <tt>Proc#call</tt>. + # + # This usage is _deprecated_. + # Use explicit multiple return values and/or multiple expectations instead. + # + # A +Proc+ instance will be treated the same as any other value in a future release. # object = mock() # object.stubs(:stubbed_method).returns(lambda { rand(100) }) # object.stubbed_method # => 41 # object.stubbed_method # => 77 def returns(*values) - @return_value = (values.size > 1) ? lambda { values.shift } : @return_value = values.first + @return_values += ReturnValues.build(*values) self end @@ -215,23 +304,51 @@ module Mocha # object = mock() # object.expects(:expected_method).raises(Exception, 'message') # object.expected_method # => raises exception of class Exception and with message 'message' + # May be called multiple times on the same expectation. Also see Expectation#then. + # object = mock() + # object.stubs(:expected_method).raises(Exception1).then.raises(Exception2) + # object.expected_method # => raises exception of class Exception1 + # object.expected_method # => raises exception of class Exception2 + # May be called in conjunction with Expectation#returns on the same expectation. + # object = mock() + # object.stubs(:expected_method).raises(Exception).then.returns(2, 3) + # object.expected_method # => raises exception of class Exception1 + # object.expected_method # => 2 + # object.expected_method # => 3 def raises(exception = RuntimeError, message = nil) - @return_value = message ? lambda { raise exception, message } : lambda { raise exception } + @return_values += ReturnValues.new(ExceptionRaiser.new(exception, message)) self end + # :call-seq: then() -> expectation + # + # Syntactic sugar to improve readability. Has no effect on state of the expectation. + # object = mock() + # object.stubs(:expected_method).returns(1, 2).then.raises(Exception).then.returns(4) + # object.expected_method # => 1 + # object.expected_method # => 2 + # object.expected_method # => raises exception of class Exception + # object.expected_method # => 4 + def then + self + end + # :stopdoc: def invoke - @invoked += 1 - yield(*@parameters_to_yield) if yield? and block_given? - @return_value.__is_a__(Proc) ? @return_value.call : @return_value + @invoked_count += 1 + if block_given? then + @yield_parameters.next_invocation.each do |yield_parameters| + yield(*yield_parameters) + end + end + @return_values.next end def verify yield(self) if block_given? - unless (@count === @invoked) then - error = ExpectationError.new(error_message(@count, @invoked)) + unless (@expected_count === @invoked_count) then + error = ExpectationError.new(error_message(@expected_count, @invoked_count)) error.set_backtrace(filtered_backtrace) raise error end @@ -251,45 +368,11 @@ module Mocha end def error_message(expected_count, actual_count) - "#{@mock.mocha_inspect}.#{method_signature} - expected calls: #{expected_count}, actual calls: #{actual_count}" + "#{@mock.mocha_inspect}.#{method_signature} - expected calls: #{expected_count.mocha_inspect}, actual calls: #{actual_count}" end # :startdoc: end - # :stopdoc: - - class Stub < Expectation - - def verify - true - end - - end - - class MissingExpectation < Expectation - - def initialize(mock, method_name) - super - @invoked = true - end - - def verify - msg = error_message(0, 1) - similar_expectations_list = similar_expectations.collect { |expectation| expectation.method_signature }.join("\n") - msg << "\nSimilar expectations:\n#{similar_expectations_list}" unless similar_expectations.empty? - error = ExpectationError.new(msg) - error.set_backtrace(filtered_backtrace) - raise error if @invoked - end - - def similar_expectations - @mock.expectations.select { |expectation| expectation.method_name == self.method_name } - end - - end - - # :startdoc: - end
\ No newline at end of file diff --git a/test/lib/mocha/infinite_range.rb b/test/lib/mocha/infinite_range.rb index 144861ada..05dfe559e 100644 --- a/test/lib/mocha/infinite_range.rb +++ b/test/lib/mocha/infinite_range.rb @@ -12,15 +12,13 @@ class Range 1/0.0 end - alias_method :__to_s__, :to_s - - def to_s - if first.to_f.infinite? then + def mocha_inspect + if first.respond_to?(:to_f) and first.to_f.infinite? then return "at most #{last}" - elsif last.to_f.infinite? then + elsif last.respond_to?(:to_f) and last.to_f.infinite? then return "at least #{first}" else - __to_s__ + to_s end end diff --git a/test/lib/mocha/inspect.rb b/test/lib/mocha/inspect.rb index fb3926b65..ad82ef70e 100644 --- a/test/lib/mocha/inspect.rb +++ b/test/lib/mocha/inspect.rb @@ -2,7 +2,9 @@ require 'date' class Object def mocha_inspect - inspect =~ /#</ ? "#<#{self.class}:0x#{self.__id__.to_s(16)}>" : inspect + address = self.__id__ * 2 + address += 0x100000000 if address < 0 + inspect =~ /#</ ? "#<#{self.class}:0x#{'%x' % address}>" : inspect end end diff --git a/test/lib/mocha/is_a.rb b/test/lib/mocha/is_a.rb new file mode 100644 index 000000000..ee23c86a9 --- /dev/null +++ b/test/lib/mocha/is_a.rb @@ -0,0 +1,9 @@ +class Object + + # :stopdoc: + + alias_method :__is_a__, :is_a? + + # :startdoc: + +end diff --git a/test/lib/mocha/missing_expectation.rb b/test/lib/mocha/missing_expectation.rb new file mode 100644 index 000000000..f84227d1a --- /dev/null +++ b/test/lib/mocha/missing_expectation.rb @@ -0,0 +1,27 @@ +require 'mocha/expectation' + +module Mocha # :nodoc: + + class MissingExpectation < Expectation # :nodoc: + + def initialize(mock, method_name) + super + @invoked_count = true + end + + def verify + msg = error_message(0, 1) + similar_expectations_list = similar_expectations.collect { |expectation| expectation.method_signature }.join("\n") + msg << "\nSimilar expectations:\n#{similar_expectations_list}" unless similar_expectations.empty? + error = ExpectationError.new(msg) + error.set_backtrace(filtered_backtrace) + raise error if @invoked_count + end + + def similar_expectations + @mock.expectations.select { |expectation| expectation.method_name == self.method_name } + end + + end + +end
\ No newline at end of file diff --git a/test/lib/mocha/mock.rb b/test/lib/mocha/mock.rb index 1924aa89c..18c23fede 100644 --- a/test/lib/mocha/mock.rb +++ b/test/lib/mocha/mock.rb @@ -1,19 +1,206 @@ -require 'mocha/mock_methods' +require 'mocha/expectation' +require 'mocha/stub' +require 'mocha/missing_expectation' +require 'mocha/metaclass' -module Mocha +module Mocha # :nodoc: + # Traditional mock object. + # + # Methods return an Expectation which can be further modified by methods on Expectation. class Mock - include MockMethods - + # :stopdoc: + def initialize(stub_everything = false, name = nil) @stub_everything = stub_everything @mock_name = name + @expectations = [] + @responder = nil + end + + attr_reader :stub_everything, :expectations + + # :startdoc: + + # :call-seq: expects(method_name) -> expectation + # expects(method_names) -> last expectation + # + # Adds an expectation that a method identified by +method_name+ symbol 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+ 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 <tt>\_\_expects\_\_</tt> + def expects(method_name_or_hash, backtrace = nil) + if method_name_or_hash.is_a?(Hash) then + method_name_or_hash.each do |method_name, return_value| + add_expectation(Expectation.new(self, method_name, backtrace).returns(return_value)) + end + else + add_expectation(Expectation.new(self, method_name_or_hash, backtrace)) + end + end + + # :call-seq: stubs(method_name) -> expectation + # stubs(method_names) -> last expectation + # + # Adds an expectation that a method identified by +method_name+ symbol 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+ 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 <tt>\_\_stubs\_\_</tt> + def stubs(method_name_or_hash, backtrace = nil) + if method_name_or_hash.is_a?(Hash) then + method_name_or_hash.each do |method_name, return_value| + add_expectation(Stub.new(self, method_name, backtrace).returns(return_value)) + end + else + add_expectation(Stub.new(self, method_name_or_hash, backtrace)) + end + 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 <tt>respond_to?</tt> a method invocation (even if the method has been expected or stubbed). + # + # The +mock+ will delegate its <tt>respond_to?</tt> 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: + + alias_method :__expects__, :expects + alias_method :__stubs__, :stubs + + alias_method :quacks_like, :responds_like + + def add_expectation(expectation) + @expectations << expectation + method_name = expectation.method_name + self.__metaclass__.send(:undef_method, method_name) if self.__metaclass__.method_defined?(method_name) + expectation + 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 + matching_expectation = matching_expectation(symbol, *arguments) + if matching_expectation then + matching_expectation.invoke(&block) + elsif stub_everything then + return + else + begin + super_method_missing(symbol, *arguments, &block) + rescue NoMethodError + unexpected_method_called(symbol, *arguments) + end + end + end + + def respond_to?(symbol) + if @responder then + @responder.respond_to?(symbol) + else + @expectations.any? { |expectation| expectation.method_name == symbol } + end + end + + def super_method_missing(symbol, *arguments, &block) + raise NoMethodError + end + + def unexpected_method_called(symbol, *arguments) + MissingExpectation.new(self, symbol).with(*arguments).verify + end + + def matching_expectation(symbol, *arguments) + @expectations.reverse.detect { |expectation| expectation.match?(symbol, *arguments) } + end + + def verify(&block) + @expectations.each { |expectation| expectation.verify(&block) } + end + def mocha_inspect - @mock_name ? "#<Mock:#{@mock_name}>" : "#<Mock:0x#{__id__.to_s(16)}>" + address = self.__id__ * 2 + address += 0x100000000 if address < 0 + @mock_name ? "#<Mock:#{@mock_name}>" : "#<Mock:0x#{'%x' % address}>" end + + def inspect + mocha_inspect + end + + # :startdoc: end diff --git a/test/lib/mocha/mock_methods.rb b/test/lib/mocha/mock_methods.rb deleted file mode 100644 index e2f5a114e..000000000 --- a/test/lib/mocha/mock_methods.rb +++ /dev/null @@ -1,122 +0,0 @@ -require 'mocha/expectation' -require 'mocha/metaclass' - -module Mocha - # Methods added to mock objects. - # These methods all return an expectation which can be further modified by methods on Mocha::Expectation. - module MockMethods - - # :stopdoc: - - attr_reader :stub_everything - - def expectations - @expectations ||= [] - end - - # :startdoc: - - # :call-seq: expects(method_name) -> expectation - # expects(method_names) -> last expectation - # - # Adds an expectation that a method identified by +method_name+ symbol must be called exactly once with any parameters. - # Returns the new expectation which can be further modified by methods on Mocha::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+ 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) - def expects(method_names, backtrace = nil) - method_names = method_names.is_a?(Hash) ? method_names : { method_names => nil } - method_names.each do |method_name, return_value| - expectations << Expectation.new(self, method_name, backtrace).returns(return_value) - self.__metaclass__.send(:undef_method, method_name) if self.__metaclass__.method_defined?(method_name) - end - expectations.last - end - - alias_method :__expects__, :expects - - # :call-seq: stubs(method_name) -> expectation - # stubs(method_names) -> last expectation - # - # Adds an expectation that a method identified by +method_name+ symbol may be called any number of times with any parameters. - # Returns the new expectation which can be further modified by methods on Mocha::Expectation. - # object = mock() - # object.stubs(:method1) - # object.method1 - # object.method1 - # # no error raised - # If +method_names+ 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) - def stubs(method_names, backtrace = nil) - method_names = method_names.is_a?(Hash) ? method_names : { method_names => nil } - method_names.each do |method_name, return_value| - expectations << Stub.new(self, method_name, backtrace).returns(return_value) - self.__metaclass__.send(:undef_method, method_name) if self.__metaclass__.method_defined?(method_name) - end - expectations.last - end - - alias_method :__stubs__, :stubs - - # :stopdoc: - - def method_missing(symbol, *arguments, &block) - matching_expectation = matching_expectation(symbol, *arguments) - if matching_expectation then - matching_expectation.invoke(&block) - elsif stub_everything then - return - else - begin - super_method_missing(symbol, *arguments, &block) - rescue NoMethodError - unexpected_method_called(symbol, *arguments) - end - end - end - - def respond_to?(symbol) - expectations.any? { |expectation| expectation.method_name == symbol } - end - - def super_method_missing(symbol, *arguments, &block) - raise NoMethodError - end - - def unexpected_method_called(symbol, *arguments) - MissingExpectation.new(self, symbol).with(*arguments).verify - end - - def matching_expectation(symbol, *arguments) - expectations.reverse.detect { |expectation| expectation.match?(symbol, *arguments) } - end - - def verify(&block) - expectations.each { |expectation| expectation.verify(&block) } - end - - # :startdoc: - - end -end
\ No newline at end of file diff --git a/test/lib/mocha/multiple_yields.rb b/test/lib/mocha/multiple_yields.rb new file mode 100644 index 000000000..8186c3076 --- /dev/null +++ b/test/lib/mocha/multiple_yields.rb @@ -0,0 +1,20 @@ +module Mocha # :nodoc: + + class MultipleYields # :nodoc: + + attr_reader :parameter_groups + + def initialize(*parameter_groups) + @parameter_groups = parameter_groups + end + + def each + @parameter_groups.each do |parameter_group| + yield(parameter_group) + end + end + + end + +end + diff --git a/test/lib/mocha/no_yields.rb b/test/lib/mocha/no_yields.rb new file mode 100644 index 000000000..b0fba415d --- /dev/null +++ b/test/lib/mocha/no_yields.rb @@ -0,0 +1,11 @@ +module Mocha # :nodoc: + + class NoYields # :nodoc: + + def each + end + + end + +end + diff --git a/test/lib/mocha/object.rb b/test/lib/mocha/object.rb index 7b1d6b83e..7ccdbad0d 100644 --- a/test/lib/mocha/object.rb +++ b/test/lib/mocha/object.rb @@ -3,7 +3,9 @@ require 'mocha/instance_method' require 'mocha/class_method' require 'mocha/any_instance_method' -# Methods added all Objects. +# Methods added all objects to allow mocking and stubbing on real objects. +# +# Methods return a Mocha::Expectation which can be further modified by methods on Mocha::Expectation. class Object def mocha # :nodoc: @@ -29,6 +31,10 @@ class Object # product = Product.new # product.expects(:save).returns(true) # assert_equal false, product.save + # + # The original implementation of <tt>Product#save</tt> is replaced temporarily. + # + # The original implementation of <tt>Product#save</tt> is restored at the end of the test. def expects(symbol) method = stubba_method.new(stubba_object, symbol) $stubba.stub(method) @@ -42,6 +48,10 @@ class Object # product = Product.new # product.stubs(:save).returns(true) # assert_equal false, product.save + # + # The original implementation of <tt>Product#save</tt> is replaced temporarily. + # + # The original implementation of <tt>Product#save</tt> is restored at the end of the test. def stubs(symbol) method = stubba_method.new(stubba_object, symbol) $stubba.stub(method) diff --git a/test/lib/mocha/parameter_matchers.rb b/test/lib/mocha/parameter_matchers.rb new file mode 100644 index 000000000..193f77d93 --- /dev/null +++ b/test/lib/mocha/parameter_matchers.rb @@ -0,0 +1,9 @@ +module Mocha + + # Used as parameters for Expectation#with to restrict the parameter values which will match the expectation. + module ParameterMatchers; end + +end + + +Dir[File.expand_path(File.join(File.dirname(__FILE__), 'parameter_matchers', "*.rb"))].each { |lib| require lib } diff --git a/test/lib/mocha/parameter_matchers/all_of.rb b/test/lib/mocha/parameter_matchers/all_of.rb new file mode 100644 index 000000000..343d9eea0 --- /dev/null +++ b/test/lib/mocha/parameter_matchers/all_of.rb @@ -0,0 +1,39 @@ +module Mocha + + module ParameterMatchers + + # :call-seq: all_of -> parameter_matcher + # + # Matches if all +matchers+ match. + # object = mock() + # object.expects(:method_1).with(all_of(includes(1), includes(3))) + # object.method_1([1, 3]) + # # no error raised + # + # object = mock() + # object.expects(:method_1).with(all_of(includes(1), includes(3))) + # object.method_1([1, 2]) + # # error raised, because method_1 was not called with object including 1 and 3 + def all_of(*matchers) + AllOf.new(*matchers) + end + + class AllOf # :nodoc: + + def initialize(*matchers) + @matchers = matchers + end + + def ==(parameter) + @matchers.all? { |matcher| matcher == parameter } + end + + def mocha_inspect + "all_of(#{@matchers.map { |matcher| matcher.mocha_inspect }.join(", ") })" + end + + end + + end + +end
\ No newline at end of file diff --git a/test/lib/mocha/parameter_matchers/any_of.rb b/test/lib/mocha/parameter_matchers/any_of.rb new file mode 100644 index 000000000..a1f88075d --- /dev/null +++ b/test/lib/mocha/parameter_matchers/any_of.rb @@ -0,0 +1,44 @@ +module Mocha + + module ParameterMatchers + + # :call-seq: any_of -> parameter_matcher + # + # Matches if any +matchers+ match. + # object = mock() + # object.expects(:method_1).with(any_of(1, 3)) + # object.method_1(1) + # # no error raised + # + # object = mock() + # object.expects(:method_1).with(any_of(1, 3)) + # object.method_1(3) + # # no error raised + # + # object = mock() + # object.expects(:method_1).with(any_of(1, 3)) + # object.method_1(2) + # # error raised, because method_1 was not called with 1 or 3 + def any_of(*matchers) + AnyOf.new(*matchers) + end + + class AnyOf # :nodoc: + + def initialize(*matchers) + @matchers = matchers + end + + def ==(parameter) + @matchers.any? { |matcher| matcher == parameter } + end + + def mocha_inspect + "any_of(#{@matchers.map { |matcher| matcher.mocha_inspect }.join(", ") })" + end + + end + + end + +end
\ No newline at end of file diff --git a/test/lib/mocha/parameter_matchers/anything.rb b/test/lib/mocha/parameter_matchers/anything.rb new file mode 100644 index 000000000..57d0eeab4 --- /dev/null +++ b/test/lib/mocha/parameter_matchers/anything.rb @@ -0,0 +1,30 @@ +module Mocha + + module ParameterMatchers + + # :call-seq: anything -> parameter_matcher + # + # Matches any object. + # object = mock() + # object.expects(:method_1).with(anything) + # object.method_1('foo') + # # no error raised + def anything + Anything.new + end + + class Anything # :nodoc: + + def ==(parameter) + return true + end + + def mocha_inspect + "anything" + end + + end + + end + +end
\ No newline at end of file diff --git a/test/lib/mocha/parameter_matchers/has_entry.rb b/test/lib/mocha/parameter_matchers/has_entry.rb new file mode 100644 index 000000000..3d7cac4e6 --- /dev/null +++ b/test/lib/mocha/parameter_matchers/has_entry.rb @@ -0,0 +1,39 @@ +module Mocha + + module ParameterMatchers + + # :call-seq: has_entry(key, value) -> parameter_matcher + # + # Matches +Hash+ containing entry with +key+ and +value+. + # object = mock() + # object.expects(:method_1).with(has_entry('key_1', 1)) + # object.method_1('key_1' => 1, 'key_2' => 2) + # # no error raised + # + # object = mock() + # object.expects(:method_1).with(has_entry('key_1', 1)) + # object.method_1('key_1' => 2, 'key_2' => 1) + # # error raised, because method_1 was not called with Hash containing entry: 'key_1' => 1 + def has_entry(key, value) + HasEntry.new(key, value) + end + + class HasEntry # :nodoc: + + def initialize(key, value) + @key, @value = key, value + end + + def ==(parameter) + parameter[@key] == @value + end + + def mocha_inspect + "has_entry(#{@key.mocha_inspect}, #{@value.mocha_inspect})" + end + + end + + end + +end
\ No newline at end of file diff --git a/test/lib/mocha/parameter_matchers/has_key.rb b/test/lib/mocha/parameter_matchers/has_key.rb new file mode 100644 index 000000000..5a1fcd2e8 --- /dev/null +++ b/test/lib/mocha/parameter_matchers/has_key.rb @@ -0,0 +1,39 @@ +module Mocha + + module ParameterMatchers + + # :call-seq: has_key(key) -> parameter_matcher + # + # Matches +Hash+ containing +key+. + # object = mock() + # object.expects(:method_1).with(has_key('key_1')) + # object.method_1('key_1' => 1, 'key_2' => 2) + # # no error raised + # + # object = mock() + # object.expects(:method_1).with(has_key('key_1')) + # object.method_1('key_2' => 2) + # # error raised, because method_1 was not called with Hash containing key: 'key_1' + def has_key(key) + HasKey.new(key) + end + + class HasKey # :nodoc: + + def initialize(key) + @key = key + end + + def ==(parameter) + parameter.keys.include?(@key) + end + + def mocha_inspect + "has_key(#{@key.mocha_inspect})" + end + + end + + end + +end
\ No newline at end of file diff --git a/test/lib/mocha/parameter_matchers/has_value.rb b/test/lib/mocha/parameter_matchers/has_value.rb new file mode 100644 index 000000000..742f84268 --- /dev/null +++ b/test/lib/mocha/parameter_matchers/has_value.rb @@ -0,0 +1,39 @@ +module Mocha + + module ParameterMatchers + + # :call-seq: has_value(value) -> parameter_matcher + # + # Matches +Hash+ containing +value+. + # object = mock() + # object.expects(:method_1).with(has_value(1)) + # object.method_1('key_1' => 1, 'key_2' => 2) + # # no error raised + # + # object = mock() + # object.expects(:method_1).with(has_value(1)) + # object.method_1('key_2' => 2) + # # error raised, because method_1 was not called with Hash containing value: 1 + def has_value(value) + HasValue.new(value) + end + + class HasValue # :nodoc: + + def initialize(value) + @value = value + end + + def ==(parameter) + parameter.values.include?(@value) + end + + def mocha_inspect + "has_value(#{@value.mocha_inspect})" + end + + end + + end + +end
\ No newline at end of file diff --git a/test/lib/mocha/parameter_matchers/includes.rb b/test/lib/mocha/parameter_matchers/includes.rb new file mode 100644 index 000000000..0e4fbe960 --- /dev/null +++ b/test/lib/mocha/parameter_matchers/includes.rb @@ -0,0 +1,37 @@ +module Mocha + + module ParameterMatchers + + # :call-seq: includes(item) -> parameter_matcher + # + # Matches any object that responds true to include?(item) + # object = mock() + # object.expects(:method_1).with(includes('foo')) + # object.method_1(['foo', 'bar']) + # # no error raised + # + # object.method_1(['baz']) + # # error raised, because ['baz'] does not include 'foo'. + def includes(item) + Includes.new(item) + end + + class Includes # :nodoc: + + def initialize(item) + @item = item + end + + def ==(parameter) + return parameter.include?(@item) + end + + def mocha_inspect + "includes(#{@item.mocha_inspect})" + end + + end + + end + +end diff --git a/test/lib/mocha/return_values.rb b/test/lib/mocha/return_values.rb new file mode 100644 index 000000000..ea0fbbd40 --- /dev/null +++ b/test/lib/mocha/return_values.rb @@ -0,0 +1,31 @@ +require 'mocha/single_return_value' + +module Mocha # :nodoc: + + class ReturnValues # :nodoc: + + def self.build(*values) + new(*values.map { |value| SingleReturnValue.new(value) }) + end + + attr_accessor :values + + def initialize(*values) + @values = values + end + + def next + case @values.size + when 0: nil + when 1: @values.first.evaluate + else @values.shift.evaluate + end + end + + def +(other) + self.class.new(*(@values + other.values)) + end + + end + +end
\ No newline at end of file diff --git a/test/lib/mocha/single_return_value.rb b/test/lib/mocha/single_return_value.rb new file mode 100644 index 000000000..f420b8b8c --- /dev/null +++ b/test/lib/mocha/single_return_value.rb @@ -0,0 +1,24 @@ +require 'mocha/is_a' +require 'mocha/deprecation' + +module Mocha # :nodoc: + + class SingleReturnValue # :nodoc: + + def initialize(value) + @value = value + end + + def evaluate + if @value.__is_a__(Proc) then + message = 'use of Expectation#returns with instance of Proc - see Expectation#returns RDoc for alternatives' + Deprecation.warning(message) + @value.call + else + @value + end + end + + end + +end
\ No newline at end of file diff --git a/test/lib/mocha/single_yield.rb b/test/lib/mocha/single_yield.rb new file mode 100644 index 000000000..5af571621 --- /dev/null +++ b/test/lib/mocha/single_yield.rb @@ -0,0 +1,18 @@ +module Mocha # :nodoc: + + class SingleYield # :nodoc: + + attr_reader :parameters + + def initialize(*parameters) + @parameters = parameters + end + + def each + yield(@parameters) + end + + end + +end + diff --git a/test/lib/mocha/standalone.rb b/test/lib/mocha/standalone.rb index 127ede641..8e3a7cefc 100644 --- a/test/lib/mocha/standalone.rb +++ b/test/lib/mocha/standalone.rb @@ -1,4 +1,5 @@ require 'mocha/auto_verify' +require 'mocha/parameter_matchers' require 'mocha/setup_and_teardown' module Mocha @@ -6,6 +7,7 @@ module Mocha module Standalone include AutoVerify + include ParameterMatchers include SetupAndTeardown def mocha_setup diff --git a/test/lib/mocha/stub.rb b/test/lib/mocha/stub.rb new file mode 100644 index 000000000..1b3cccb8a --- /dev/null +++ b/test/lib/mocha/stub.rb @@ -0,0 +1,18 @@ +require 'mocha/expectation' + +module Mocha # :nodoc: + + class Stub < Expectation # :nodoc: + + def initialize(mock, method_name, backtrace = nil) + super + @expected_count = Range.at_least(0) + end + + def verify + true + end + + end + +end
\ No newline at end of file diff --git a/test/lib/mocha/test_case_adapter.rb b/test/lib/mocha/test_case_adapter.rb index 100a7a514..dc7e33b68 100644 --- a/test/lib/mocha/test_case_adapter.rb +++ b/test/lib/mocha/test_case_adapter.rb @@ -22,8 +22,8 @@ module Mocha add_failure(e.message, e.backtrace) rescue Test::Unit::AssertionFailedError => e add_failure(e.message, e.backtrace) - rescue StandardError, ScriptError => e - add_error(e) + rescue StandardError, ScriptError + add_error($!) ensure begin teardown @@ -46,4 +46,4 @@ module Mocha end -end +end
\ No newline at end of file diff --git a/test/lib/mocha/yield_parameters.rb b/test/lib/mocha/yield_parameters.rb new file mode 100644 index 000000000..b1623bf71 --- /dev/null +++ b/test/lib/mocha/yield_parameters.rb @@ -0,0 +1,31 @@ +require 'mocha/no_yields' +require 'mocha/single_yield' +require 'mocha/multiple_yields' + +module Mocha # :nodoc: + + class YieldParameters # :nodoc: + + def initialize + @parameter_groups = [] + end + + def next_invocation + case @parameter_groups.size + when 0: NoYields.new + when 1: @parameter_groups.first + else @parameter_groups.shift + end + end + + def add(*parameters) + @parameter_groups << SingleYield.new(*parameters) + end + + def multiple_add(*parameter_groups) + @parameter_groups << MultipleYields.new(*parameter_groups) + end + + end + +end
\ No newline at end of file |
