diff --git a/lib/mocha/mock.rb b/lib/mocha/mock.rb index 08e0199..704a117 100644 --- a/lib/mocha/mock.rb +++ b/lib/mocha/mock.rb @@ -5,13 +5,14 @@ require 'mocha/missing_expectation' require 'mocha/metaclass' 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_name(*args) -> 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. @@ -34,18 +35,26 @@ module Mocha # :nodoc: # object.expects(:method1).returns(:result1) # object.expects(:method2).returns(:result2) # + # Additionally, a pseudo-call syntax is provided for convenience, such that the following + # lines are equivalent: + # object.expects.method1("some", :values) + # object.expects(:method1).with("some", :values) + # # Aliased by \_\_expects\_\_ - def expects(method_name_or_hash, backtrace = nil) + def expects(method_name_or_hash = nil, 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 + elsif method_name_or_hash.nil? + PseudoCall.new(self, Expectation) else add_expectation(Expectation.new(self, method_name_or_hash, backtrace)) end end - + # :call-seq: stubs(method_name) -> expectation + # stubs.method_name(*args) -> 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. @@ -65,17 +74,24 @@ module Mocha # :nodoc: # object.stubs(:method1).returns(:result1) # object.stubs(:method2).returns(:result2) # + # Additionally, a pseudo-call syntax is provided for convenience, such that the following + # lines are equivalent: + # object.stubs.method1("some", :values) + # object.stubs(:method1).with("some", :values) + # # Aliased by \_\_stubs\_\_ - def stubs(method_name_or_hash, backtrace = nil) + def stubs(method_name_or_hash = nil, 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 + elsif method_name_or_hash.nil? + PseudoCall.new(self, Stub) 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. @@ -120,9 +136,9 @@ module Mocha # :nodoc: @responder = object self end - + # :stopdoc: - + def initialize(stub_everything = false, name = nil) @stub_everything = stub_everything @mock_name = name @@ -131,11 +147,11 @@ module Mocha # :nodoc: end attr_reader :stub_everything, :expectations - + alias_method :__expects__, :expects alias_method :__stubs__, :stubs - + alias_method :quacks_like, :responds_like def add_expectation(expectation) @@ -158,7 +174,7 @@ module Mocha # :nodoc: unexpected_method_called(symbol, *arguments) end end - + def respond_to?(symbol) if @responder then @responder.respond_to?(symbol) @@ -166,21 +182,21 @@ module Mocha # :nodoc: @expectations.respond_to?(symbol) end end - + def unexpected_method_called(symbol, *arguments) MissingExpectation.new(self, symbol).with(*arguments).verify end - + def verify(&block) @expectations.verify(&block) end - + def mocha_inspect address = self.__id__ * 2 address += 0x100000000 if address < 0 @mock_name ? "#" : "#" end - + def inspect mocha_inspect end @@ -193,4 +209,17 @@ module Mocha # :nodoc: end + class PseudoCall + # Turn this class into a blank slate + instance_methods.each { |m| undef_method m unless m =~ /^__/ } + + def initialize(mock, expectation_class) + @mock = mock + @expectation_class = expectation_class + end + + def method_missing(method_name, *args) + @mock.add_expectation(@expectation_class.new(@mock, method_name, nil)) + end + end end diff --git a/test/unit/mock_test.rb b/test/unit/mock_test.rb index a1710a5..ddc0e82 100644 --- a/test/unit/mock_test.rb +++ b/test/unit/mock_test.rb @@ -4,16 +4,16 @@ require 'mocha/expectation_error' require 'set' class MockTest < Test::Unit::TestCase - + include Mocha - + def test_should_set_single_expectation mock = Mock.new mock.expects(:method1).returns(1) assert_nothing_raised(ExpectationError) do assert_equal 1, mock.method1 end - end + end def test_should_build_and_store_expectations mock = Mock.new @@ -21,22 +21,22 @@ class MockTest < Test::Unit::TestCase assert_not_nil expectation assert_equal [expectation], mock.expectations.to_a end - + def test_should_not_stub_everything_by_default mock = Mock.new assert_equal false, mock.stub_everything end - + def test_should_stub_everything mock = Mock.new(stub_everything = true) assert_equal true, mock.stub_everything end - + def test_should_display_object_id_for_mocha_inspect_if_mock_has_no_name mock = Mock.new assert_match Regexp.new("^#$"), mock.mocha_inspect end - + def test_should_display_name_for_mocha_inspect_if_mock_has_name mock = Mock.new(false, 'named_mock') assert_equal "#", mock.mocha_inspect @@ -46,7 +46,7 @@ class MockTest < Test::Unit::TestCase mock = Mock.new assert_match Regexp.new("^#$"), mock.inspect end - + def test_should_display_name_for_inspect_if_mock_has_name mock = Mock.new(false, 'named_mock') assert_equal "#", mock.inspect @@ -56,12 +56,12 @@ class MockTest < Test::Unit::TestCase mock = Mock.new assert_nothing_raised(ExpectationError) { mock.extend(Module.new) } end - + def test_should_be_equal mock = Mock.new assert_equal true, mock.eql?(mock) end - + def test_should_be_able_to_mock_standard_object_methods mock = Mock.new object_methods = STANDARD_OBJECT_PUBLIC_INSTANCE_METHODS.reject { |m| m =~ /^__.*__$/ }.sort @@ -76,42 +76,42 @@ class MockTest < Test::Unit::TestCase object_methods.each { |method| mock.__stubs__(method.to_sym).returns(method) } object_methods.each { |method| assert_equal method, mock.__send__(method.to_sym) } end - + def test_should_create_and_add_expectations mock = Mock.new expectation1 = mock.expects(:method1) expectation2 = mock.expects(:method2) assert_equal [expectation1, expectation2].to_set, mock.expectations.to_set end - + def test_should_pass_backtrace_into_expectation mock = Mock.new backtrace = Object.new expectation = mock.expects(:method1, backtrace) assert_equal backtrace, expectation.backtrace end - + def test_should_pass_backtrace_into_stub mock = Mock.new backtrace = Object.new stub = mock.stubs(:method1, backtrace) assert_equal backtrace, stub.backtrace end - + def test_should_create_and_add_stubs mock = Mock.new stub1 = mock.stubs(:method1) stub2 = mock.stubs(:method2) assert_equal [stub1, stub2].to_set, mock.expectations.to_set end - + def test_should_invoke_expectation_and_return_result mock = Mock.new mock.expects(:my_method).returns(:result) result = mock.my_method assert_equal :result, result end - + def test_should_not_raise_error_if_stubbing_everything mock = Mock.new(stub_everything = true) result = nil @@ -120,7 +120,7 @@ class MockTest < Test::Unit::TestCase end assert_nil result end - + def test_should_raise_assertion_error_for_unexpected_method_call mock = Mock.new error = assert_raise(ExpectationError) do @@ -130,7 +130,7 @@ class MockTest < Test::Unit::TestCase assert_match(/argument1/, error.message) assert_match(/argument2/, error.message) end - + def test_should_indicate_unexpected_method_called mock = Mock.new class << mock @@ -143,7 +143,7 @@ class MockTest < Test::Unit::TestCase assert_equal :my_method, mock.symbol assert_equal [:argument1, :argument2], mock.arguments end - + def test_should_verify_that_all_expectations_have_been_fulfilled mock = Mock.new mock.expects(:method1) @@ -153,14 +153,14 @@ class MockTest < Test::Unit::TestCase mock.verify end end - + def test_should_report_possible_expectations mock = Mock.new mock.expects(:expected_method).with(1) exception = assert_raise(ExpectationError) { mock.expected_method(2) } assert_equal "#{mock.mocha_inspect}.expected_method(2) - expected calls: 0, actual calls: 1\nSimilar expectations:\n#{mock.mocha_inspect}.expected_method(1)", exception.message end - + def test_should_pass_block_through_to_expectations_verify_method mock = Mock.new expected_expectation = mock.expects(:method1) @@ -169,7 +169,7 @@ class MockTest < Test::Unit::TestCase mock.verify() { |expectation| expectations << expectation } assert_equal [expected_expectation], expectations end - + def test_should_yield_supplied_parameters_to_block mock = Mock.new parameters_for_yield = [1, 2, 3] @@ -178,35 +178,35 @@ class MockTest < Test::Unit::TestCase mock.method1() { |*parameters| yielded_parameters = parameters } assert_equal parameters_for_yield, yielded_parameters end - + def test_should_set_up_multiple_expectations_with_return_values mock = Mock.new mock.expects(:method1 => :result1, :method2 => :result2) assert_equal :result1, mock.method1 assert_equal :result2, mock.method2 end - + def test_should_set_up_multiple_stubs_with_return_values mock = Mock.new mock.stubs(:method1 => :result1, :method2 => :result2) assert_equal :result1, mock.method1 assert_equal :result2, mock.method2 end - + def test_should_keep_returning_specified_value_for_stubs mock = Mock.new mock.stubs(:method1).returns(1) assert_equal 1, mock.method1 assert_equal 1, mock.method1 end - + def test_should_keep_returning_specified_value_for_expects mock = Mock.new mock.expects(:method1).times(2).returns(1) assert_equal 1, mock.method1 assert_equal 1, mock.method1 end - + def test_should_match_most_recent_call_to_expects mock = Mock.new mock.expects(:method1).returns(0) @@ -234,18 +234,18 @@ class MockTest < Test::Unit::TestCase mock.stubs(:method1).returns(1) assert_equal 1, mock.method1 end - + def test_should_respond_to_expected_method mock = Mock.new mock.expects(:method1) assert_equal true, mock.respond_to?(:method1) end - + def test_should_not_respond_to_unexpected_method mock = Mock.new assert_equal false, mock.respond_to?(:method1) end - + def test_should_respond_to_methods_which_the_responder_does_responds_to instance = Class.new do define_method(:respond_to?) { true } @@ -254,7 +254,7 @@ class MockTest < Test::Unit::TestCase mock.responds_like(instance) assert_equal true, mock.respond_to?(:invoked_method) end - + def test_should_not_respond_to_methods_which_the_responder_does_not_responds_to instance = Class.new do define_method(:respond_to?) { false } @@ -263,12 +263,12 @@ class MockTest < Test::Unit::TestCase mock.responds_like(instance) assert_equal false, mock.respond_to?(:invoked_method) end - + def test_should_return_itself_to_allow_method_chaining mock = Mock.new assert_same mock.responds_like(Object.new), mock end - + def test_should_not_raise_no_method_error_if_mock_is_not_restricted_to_respond_like_a_responder instance = Class.new do define_method(:respond_to?) { true } @@ -277,7 +277,7 @@ class MockTest < Test::Unit::TestCase mock.stubs(:invoked_method) assert_nothing_raised(NoMethodError) { mock.invoked_method } end - + def test_should_not_raise_no_method_error_if_responder_does_respond_to_invoked_method instance = Class.new do define_method(:respond_to?) { true } @@ -287,7 +287,7 @@ class MockTest < Test::Unit::TestCase mock.stubs(:invoked_method) assert_nothing_raised(NoMethodError) { mock.invoked_method } end - + def test_should_raise_no_method_error_if_responder_does_not_respond_to_invoked_method instance = Class.new do define_method(:respond_to?) { false } @@ -298,7 +298,7 @@ class MockTest < Test::Unit::TestCase mock.stubs(:invoked_method) assert_raises(NoMethodError) { mock.invoked_method } end - + def test_should_raise_no_method_error_with_message_indicating_that_mock_is_constrained_to_respond_like_responder instance = Class.new do define_method(:respond_to?) { false } @@ -313,4 +313,26 @@ class MockTest < Test::Unit::TestCase assert_match(/which responds like mocha_inspect/, e.message) end end -end \ No newline at end of file + + def test_should_create_and_store_expectations_given_as_pseudo_calls + mock = Mock.new + expectation1 = mock.expects.method1 + expectation2 = mock.expects.method2 + assert_equal [expectation1, expectation2].to_set, mock.expectations.to_set + end + + def test_should_create_and_add_stubs_given_as_pseudo_calls + mock = Mock.new + stub1 = mock.stubs.method1 + stub2 = mock.stubs.method2 + assert_equal [stub1, stub2].to_set, mock.expectations.to_set + end + + def test_should_treat_pseudo_call_parameters_as_matcher_parameters + mock = Mock.new + expectation = mock.expects.method1(1, "two", :three) + stub = mock.expects.method2(:four) + assert expectation.match?(:method1, 1, "two", :three) + assert stub.match?(:method2, :four) + end +end