From 7149c23cadbed81171d87fb53eb0c1fcd88ffc90 Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Wed, 23 Oct 2019 20:17:00 +0100 Subject: [PATCH 01/28] display return values of invocations --- lib/mocha/expectation.rb | 12 +++++- ...invocations_alongside_expectations_test.rb | 38 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 test/acceptance/display_matching_invocations_alongside_expectations_test.rb diff --git a/lib/mocha/expectation.rb b/lib/mocha/expectation.rb index 638db7d45..21b1b9029 100644 --- a/lib/mocha/expectation.rb +++ b/lib/mocha/expectation.rb @@ -516,6 +516,7 @@ def initialize(mock, expected_method_name, backtrace = nil) @return_values = ReturnValues.new @yield_parameters = YieldParameters.new @backtrace = backtrace || caller + @returned_values = [] end # @private @@ -570,7 +571,9 @@ def invoke @yield_parameters.next_invocation.each do |yield_parameters| yield(*yield_parameters) end - @return_values.next + returned_value = @return_values.next + @returned_values << returned_value + returned_value end # @private @@ -603,6 +606,7 @@ def mocha_inspect message << ': ' message << method_signature message << "; #{@ordering_constraints.map(&:mocha_inspect).join('; ')}" unless @ordering_constraints.empty? + message << invocations if (ENV['MOCHA_OPTIONS'] || '').split(',').include?('verbose') message end @@ -610,5 +614,11 @@ def mocha_inspect def method_signature "#{@mock.mocha_inspect}.#{@method_matcher.mocha_inspect}#{@parameters_matcher.mocha_inspect}" end + + private + + def invocations + @returned_values.map { |returned_value| "\n - #{method_signature} # => #{returned_value.mocha_inspect}"}.join + end end end diff --git a/test/acceptance/display_matching_invocations_alongside_expectations_test.rb b/test/acceptance/display_matching_invocations_alongside_expectations_test.rb new file mode 100644 index 000000000..992029f51 --- /dev/null +++ b/test/acceptance/display_matching_invocations_alongside_expectations_test.rb @@ -0,0 +1,38 @@ +require File.expand_path('../acceptance_test_helper', __FILE__) +require 'mocha/setup' + +class DisplayMatchingInvocationsAlongsideExpectationsTest < Mocha::TestCase + include AcceptanceTest + + def setup + setup_acceptance_test + @original_env = ENV.to_hash + ENV['MOCHA_OPTIONS'] = 'verbose' + end + + def teardown + ENV.replace(@original_env) + teardown_acceptance_test + end + + def test_should_display_return_values_of_matching_invocations + test_result = run_as_test do + foo = mock('foo') + foo.expects(:bar).with(1).returns('a') + foo.stubs(:bar).with(2).returns('b').then.returns('c') + + 2.times { foo.bar(2) } + foo.bar(3) + end + assert_failed(test_result) + assert_equal [ + 'unexpected invocation: #.bar(3)', + 'unsatisfied expectations:', + '- expected exactly once, not yet invoked: #.bar(1)', + 'satisfied expectations:', + '- allowed any number of times, invoked twice: #.bar(2)', + ' - #.bar(2) # => "b"', + ' - #.bar(2) # => "c"' + ], test_result.failure_message_lines + end +end From 84a844c3ab658e8b47a8bd4a55c2d2140d91299f Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Wed, 23 Oct 2019 21:46:26 +0100 Subject: [PATCH 02/28] display raised exceptions of invocations --- lib/mocha/expectation.rb | 15 ++++++++----- lib/mocha/raised_exception.rb | 11 ++++++++++ ...invocations_alongside_expectations_test.rb | 22 +++++++++++++++++++ 3 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 lib/mocha/raised_exception.rb diff --git a/lib/mocha/expectation.rb b/lib/mocha/expectation.rb index 21b1b9029..600fdadb5 100644 --- a/lib/mocha/expectation.rb +++ b/lib/mocha/expectation.rb @@ -9,6 +9,7 @@ require 'mocha/in_state_ordering_constraint' require 'mocha/change_state_side_effect' require 'mocha/cardinality' +require 'mocha/raised_exception' module Mocha # Methods on expectations returned from {Mock#expects}, {Mock#stubs}, {ObjectMethods#expects} and {ObjectMethods#stubs}. @@ -516,7 +517,7 @@ def initialize(mock, expected_method_name, backtrace = nil) @return_values = ReturnValues.new @yield_parameters = YieldParameters.new @backtrace = backtrace || caller - @returned_values = [] + @results = [] end # @private @@ -571,9 +572,13 @@ def invoke @yield_parameters.next_invocation.each do |yield_parameters| yield(*yield_parameters) end - returned_value = @return_values.next - @returned_values << returned_value - returned_value + begin + @results << @return_values.next + rescue Exception => e + @results << RaisedException.new(e) + raise + end + @results.last end # @private @@ -618,7 +623,7 @@ def method_signature private def invocations - @returned_values.map { |returned_value| "\n - #{method_signature} # => #{returned_value.mocha_inspect}"}.join + @results.map { |result| "\n - #{method_signature} # => #{result.mocha_inspect}"}.join end end end diff --git a/lib/mocha/raised_exception.rb b/lib/mocha/raised_exception.rb new file mode 100644 index 000000000..8a1ad9720 --- /dev/null +++ b/lib/mocha/raised_exception.rb @@ -0,0 +1,11 @@ +module Mocha + class RaisedException + def initialize(exception) + @exception = exception + end + + def mocha_inspect + "raised #{@exception}" + end + end +end diff --git a/test/acceptance/display_matching_invocations_alongside_expectations_test.rb b/test/acceptance/display_matching_invocations_alongside_expectations_test.rb index 992029f51..30b656490 100644 --- a/test/acceptance/display_matching_invocations_alongside_expectations_test.rb +++ b/test/acceptance/display_matching_invocations_alongside_expectations_test.rb @@ -35,4 +35,26 @@ def test_should_display_return_values_of_matching_invocations ' - #.bar(2) # => "c"' ], test_result.failure_message_lines end + + def test_should_display_raised_exceptions_of_matching_invocations + test_result = run_as_test do + foo = mock('foo') + foo.expects(:bar).with(1).returns('a') + foo.stubs(:bar).with(2).returns('b').then.returns('c').then.raises(StandardError) + + assert_raise(StandardError) { 3.times { foo.bar(2) } } + foo.bar(3) + end + assert_failed(test_result) + assert_equal [ + 'unexpected invocation: #.bar(3)', + 'unsatisfied expectations:', + '- expected exactly once, not yet invoked: #.bar(1)', + 'satisfied expectations:', + '- allowed any number of times, invoked 3 times: #.bar(2)', + ' - #.bar(2) # => "b"', + ' - #.bar(2) # => "c"', + ' - #.bar(2) # => raised StandardError' + ], test_result.failure_message_lines + end end From 2bd2538bd4c988526fb0cfe2f441b3f34b7dc1f1 Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Thu, 24 Oct 2019 11:57:15 +0100 Subject: [PATCH 03/28] Prepare to display yields of invocations Since an expectation can have multiple matching invocations, and an invocation can yield multiple times, we will need to remember the sequence of yields per invocation. Yields also don't correspond 1:1 with returned values or raised expectations. Therefore, remembering the results/effects of an invocation is best done by a different object. --- lib/mocha/expectation.rb | 16 ++++++---------- lib/mocha/invocation.rb | 27 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 10 deletions(-) create mode 100644 lib/mocha/invocation.rb diff --git a/lib/mocha/expectation.rb b/lib/mocha/expectation.rb index 600fdadb5..f3414b923 100644 --- a/lib/mocha/expectation.rb +++ b/lib/mocha/expectation.rb @@ -9,7 +9,7 @@ require 'mocha/in_state_ordering_constraint' require 'mocha/change_state_side_effect' require 'mocha/cardinality' -require 'mocha/raised_exception' +require 'mocha/invocation' module Mocha # Methods on expectations returned from {Mock#expects}, {Mock#stubs}, {ObjectMethods#expects} and {ObjectMethods#stubs}. @@ -517,7 +517,7 @@ def initialize(mock, expected_method_name, backtrace = nil) @return_values = ReturnValues.new @yield_parameters = YieldParameters.new @backtrace = backtrace || caller - @results = [] + @invocations = [] end # @private @@ -569,16 +569,12 @@ def satisfied? def invoke @invocation_count += 1 perform_side_effects + invocation = Invocation.new(@return_values) + @invocations << invocation @yield_parameters.next_invocation.each do |yield_parameters| yield(*yield_parameters) end - begin - @results << @return_values.next - rescue Exception => e - @results << RaisedException.new(e) - raise - end - @results.last + invocation.call end # @private @@ -623,7 +619,7 @@ def method_signature private def invocations - @results.map { |result| "\n - #{method_signature} # => #{result.mocha_inspect}"}.join + @invocations.map { |invocation| "\n - #{method_signature} # => #{invocation.mocha_inspect}" }.join end end end diff --git a/lib/mocha/invocation.rb b/lib/mocha/invocation.rb new file mode 100644 index 000000000..eff379e2b --- /dev/null +++ b/lib/mocha/invocation.rb @@ -0,0 +1,27 @@ +require 'mocha/raised_exception' + +module Mocha + class Invocation + # @private + def initialize(return_values) + @return_values = return_values + end + + # @private + def call + begin + @result = @return_values.next + # rubocop:disable Lint/RescueException + rescue Exception => e + # rubocop:enable Lint/RescueException + @result = RaisedException.new(e) + raise + end + end + + # @private + def mocha_inspect + @result.mocha_inspect + end + end +end From 24f9c11e257f4d1846f8a2bbcc27ae1bacb1105b Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Thu, 24 Oct 2019 12:09:00 +0100 Subject: [PATCH 04/28] Move yielding to invocation This will allow the yields to be captured and remembered by the invocation --- lib/mocha/expectation.rb | 7 ++----- lib/mocha/invocation.rb | 6 +++++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/mocha/expectation.rb b/lib/mocha/expectation.rb index f3414b923..b1ba81c33 100644 --- a/lib/mocha/expectation.rb +++ b/lib/mocha/expectation.rb @@ -569,12 +569,9 @@ def satisfied? def invoke @invocation_count += 1 perform_side_effects - invocation = Invocation.new(@return_values) + invocation = Invocation.new(@yield_parameters, @return_values) @invocations << invocation - @yield_parameters.next_invocation.each do |yield_parameters| - yield(*yield_parameters) - end - invocation.call + invocation.call { |*args| yield(*args) } end # @private diff --git a/lib/mocha/invocation.rb b/lib/mocha/invocation.rb index eff379e2b..ece4a8d92 100644 --- a/lib/mocha/invocation.rb +++ b/lib/mocha/invocation.rb @@ -3,12 +3,16 @@ module Mocha class Invocation # @private - def initialize(return_values) + def initialize(yield_parameters, return_values) + @yield_parameters = yield_parameters @return_values = return_values end # @private def call + @yield_parameters.next_invocation.each do |yield_parameters| + yield(*yield_parameters) + end begin @result = @return_values.next # rubocop:disable Lint/RescueException From 1ff477c8897671860cc814565c1c4a52bb71b8f5 Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Thu, 24 Oct 2019 15:25:31 +0100 Subject: [PATCH 05/28] display yields of matching invocations --- lib/mocha/invocation.rb | 7 ++++++- ...invocations_alongside_expectations_test.rb | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/mocha/invocation.rb b/lib/mocha/invocation.rb index ece4a8d92..3e99b9200 100644 --- a/lib/mocha/invocation.rb +++ b/lib/mocha/invocation.rb @@ -1,3 +1,4 @@ +require 'mocha/parameters_matcher' require 'mocha/raised_exception' module Mocha @@ -6,11 +7,13 @@ class Invocation def initialize(yield_parameters, return_values) @yield_parameters = yield_parameters @return_values = return_values + @yields = [] end # @private def call @yield_parameters.next_invocation.each do |yield_parameters| + @yields << ParametersMatcher.new(yield_parameters) yield(*yield_parameters) end begin @@ -25,7 +28,9 @@ def call # @private def mocha_inspect - @result.mocha_inspect + desc = "#{@result.mocha_inspect}" + desc << " after yielding #{@yields.map(&:mocha_inspect).join(', then ')}" if @yields.any? + desc end end end diff --git a/test/acceptance/display_matching_invocations_alongside_expectations_test.rb b/test/acceptance/display_matching_invocations_alongside_expectations_test.rb index 30b656490..4d95c1d6e 100644 --- a/test/acceptance/display_matching_invocations_alongside_expectations_test.rb +++ b/test/acceptance/display_matching_invocations_alongside_expectations_test.rb @@ -57,4 +57,24 @@ def test_should_display_raised_exceptions_of_matching_invocations ' - #.bar(2) # => raised StandardError' ], test_result.failure_message_lines end + + def test_should_display_yields_of_matching_invocations + test_result = run_as_test do + foo = mock('foo') + foo.expects(:bar).with(1).returns('a') + foo.stubs(:bar).with(2).multiple_yields(%w[b c], %w[d e]).returns('f') + + foo.bar(2) { |yielded| yielded } + foo.bar(3) + end + assert_failed(test_result) + assert_equal [ + 'unexpected invocation: #.bar(3)', + 'unsatisfied expectations:', + '- expected exactly once, not yet invoked: #.bar(1)', + 'satisfied expectations:', + '- allowed any number of times, invoked once: #.bar(2)', + ' - #.bar(2) # => "f" after yielding ("b", "c"), then ("d", "e")' + ], test_result.failure_message_lines + end end From 1976f01b4d36df1e1f7b98ac637eeedd5f51e9d1 Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Thu, 24 Oct 2019 15:54:20 +0100 Subject: [PATCH 06/28] display arguments of matching invocations previously the expected arguments were being displayed here (using Expectation#method_signature), but now the actual arguments are displayed --- lib/mocha/expectation.rb | 16 +++++++++----- lib/mocha/invocation.rb | 8 ++++--- lib/mocha/mock.rb | 4 ++-- ...invocations_alongside_expectations_test.rb | 22 +++++++++++++++++++ 4 files changed, 39 insertions(+), 11 deletions(-) diff --git a/lib/mocha/expectation.rb b/lib/mocha/expectation.rb index b1ba81c33..cec6f89c8 100644 --- a/lib/mocha/expectation.rb +++ b/lib/mocha/expectation.rb @@ -566,12 +566,12 @@ def satisfied? end # @private - def invoke + def invoke(*arguments) @invocation_count += 1 perform_side_effects - invocation = Invocation.new(@yield_parameters, @return_values) - @invocations << invocation - invocation.call { |*args| yield(*args) } + invocation = Invocation.new(method_name, @yield_parameters, @return_values) + @cardinality << invocation + invocation.call(*arguments) { |*yield_args| yield(*yield_args) } end # @private @@ -610,13 +610,17 @@ def mocha_inspect # @private def method_signature - "#{@mock.mocha_inspect}.#{@method_matcher.mocha_inspect}#{@parameters_matcher.mocha_inspect}" + "#{method_name}#{@parameters_matcher.mocha_inspect}" end private + def method_name + "#{@mock.mocha_inspect}.#{@method_matcher.mocha_inspect}" + end + def invocations - @invocations.map { |invocation| "\n - #{method_signature} # => #{invocation.mocha_inspect}" }.join + @invocations.map(&:mocha_inspect).join end end end diff --git a/lib/mocha/invocation.rb b/lib/mocha/invocation.rb index 3e99b9200..4481b43c8 100644 --- a/lib/mocha/invocation.rb +++ b/lib/mocha/invocation.rb @@ -4,14 +4,16 @@ module Mocha class Invocation # @private - def initialize(yield_parameters, return_values) + def initialize(method_name, yield_parameters, return_values) + @method_name = method_name @yield_parameters = yield_parameters @return_values = return_values @yields = [] end # @private - def call + def call(*arguments) + @arguments = ParametersMatcher.new(arguments) @yield_parameters.next_invocation.each do |yield_parameters| @yields << ParametersMatcher.new(yield_parameters) yield(*yield_parameters) @@ -28,7 +30,7 @@ def call # @private def mocha_inspect - desc = "#{@result.mocha_inspect}" + desc = "\n - #{@method_name}#{@arguments.mocha_inspect} # => #{@result.mocha_inspect}" desc << " after yielding #{@yields.map(&:mocha_inspect).join(', then ')}" if @yields.any? desc end diff --git a/lib/mocha/mock.rb b/lib/mocha/mock.rb index 11490b07c..bf8d0ed95 100644 --- a/lib/mocha/mock.rb +++ b/lib/mocha/mock.rb @@ -305,11 +305,11 @@ def method_missing(symbol, *arguments, &block) raise NoMethodError, "undefined method `#{symbol}' for #{mocha_inspect} which responds like #{@responder.mocha_inspect}" end if (matching_expectation_allowing_invocation = all_expectations.match_allowing_invocation(symbol, *arguments)) - matching_expectation_allowing_invocation.invoke(&block) + matching_expectation_allowing_invocation.invoke(*arguments, &block) elsif (matching_expectation = all_expectations.match(symbol, *arguments)) || (!matching_expectation && !@everything_stubbed) if @unexpected_invocation.nil? @unexpected_invocation = UnexpectedInvocation.new(self, symbol, *arguments) - matching_expectation.invoke(&block) if matching_expectation + matching_expectation.invoke(*arguments, &block) if matching_expectation message = @unexpected_invocation.full_description message << @mockery.mocha_inspect else diff --git a/test/acceptance/display_matching_invocations_alongside_expectations_test.rb b/test/acceptance/display_matching_invocations_alongside_expectations_test.rb index 4d95c1d6e..ea78d3c1e 100644 --- a/test/acceptance/display_matching_invocations_alongside_expectations_test.rb +++ b/test/acceptance/display_matching_invocations_alongside_expectations_test.rb @@ -77,4 +77,26 @@ def test_should_display_yields_of_matching_invocations ' - #.bar(2) # => "f" after yielding ("b", "c"), then ("d", "e")' ], test_result.failure_message_lines end + + def test_should_display_arguments_of_matching_invocations + test_result = run_as_test do + foo = mock('foo') + foo.expects(:bar).with(1).returns('a') + foo.stubs(:bar).with(any_parameters).returns('b').then.returns('c') + + 2.times { foo.bar(2, 3) } + foo.bar(3, 2) + end + assert_failed(test_result) + assert_equal [ + 'not all expectations were satisfied', + 'unsatisfied expectations:', + '- expected exactly once, not yet invoked: #.bar(1)', + 'satisfied expectations:', + '- allowed any number of times, invoked 3 times: #.bar(any_parameters)', + ' - #.bar(2, 3) # => "b"', + ' - #.bar(2, 3) # => "c"', + ' - #.bar(3, 2) # => "c"' + ], test_result.failure_message_lines + end end From de777f3fefc3504fb33015d4fa3d1e98702f87ec Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Thu, 24 Oct 2019 17:33:22 +0100 Subject: [PATCH 07/28] Reduce granularity and redundancy of tests The tests were helpful for test-driving, but not all are needed due to overlap --- ...invocations_alongside_expectations_test.rb | 73 ++++--------------- 1 file changed, 15 insertions(+), 58 deletions(-) diff --git a/test/acceptance/display_matching_invocations_alongside_expectations_test.rb b/test/acceptance/display_matching_invocations_alongside_expectations_test.rb index ea78d3c1e..0b232f3c2 100644 --- a/test/acceptance/display_matching_invocations_alongside_expectations_test.rb +++ b/test/acceptance/display_matching_invocations_alongside_expectations_test.rb @@ -15,77 +15,35 @@ def teardown teardown_acceptance_test end - def test_should_display_return_values_of_matching_invocations + def test_should_display_results_of_matching_invocations_alongside_expectations_in_verbose_mode test_result = run_as_test do foo = mock('foo') foo.expects(:bar).with(1).returns('a') - foo.stubs(:bar).with(2).returns('b').then.returns('c') + foo.stubs(:bar).with(any_parameters).returns('f').raises(StandardError) - 2.times { foo.bar(2) } - foo.bar(3) + foo.bar(1, 2) { |_ignored| } + assert_raise(StandardError) { foo.bar(3, 4) { |_ignored| } } end assert_failed(test_result) assert_equal [ - 'unexpected invocation: #.bar(3)', - 'unsatisfied expectations:', - '- expected exactly once, not yet invoked: #.bar(1)', - 'satisfied expectations:', - '- allowed any number of times, invoked twice: #.bar(2)', - ' - #.bar(2) # => "b"', - ' - #.bar(2) # => "c"' - ], test_result.failure_message_lines - end - - def test_should_display_raised_exceptions_of_matching_invocations - test_result = run_as_test do - foo = mock('foo') - foo.expects(:bar).with(1).returns('a') - foo.stubs(:bar).with(2).returns('b').then.returns('c').then.raises(StandardError) - - assert_raise(StandardError) { 3.times { foo.bar(2) } } - foo.bar(3) - end - assert_failed(test_result) - assert_equal [ - 'unexpected invocation: #.bar(3)', - 'unsatisfied expectations:', - '- expected exactly once, not yet invoked: #.bar(1)', - 'satisfied expectations:', - '- allowed any number of times, invoked 3 times: #.bar(2)', - ' - #.bar(2) # => "b"', - ' - #.bar(2) # => "c"', - ' - #.bar(2) # => raised StandardError' - ], test_result.failure_message_lines - end - - def test_should_display_yields_of_matching_invocations - test_result = run_as_test do - foo = mock('foo') - foo.expects(:bar).with(1).returns('a') - foo.stubs(:bar).with(2).multiple_yields(%w[b c], %w[d e]).returns('f') - - foo.bar(2) { |yielded| yielded } - foo.bar(3) - end - assert_failed(test_result) - assert_equal [ - 'unexpected invocation: #.bar(3)', + 'not all expectations were satisfied', 'unsatisfied expectations:', '- expected exactly once, not yet invoked: #.bar(1)', 'satisfied expectations:', - '- allowed any number of times, invoked once: #.bar(2)', - ' - #.bar(2) # => "f" after yielding ("b", "c"), then ("d", "e")' + '- allowed any number of times, invoked twice: #.bar(any_parameters)', + ' - #.bar(1, 2) # => "f"', + ' - #.bar(3, 4) # => raised StandardError' ], test_result.failure_message_lines end - def test_should_display_arguments_of_matching_invocations + def test_should_display_yields_of_matching_invocations_alongside_expectations_in_verbose_mode test_result = run_as_test do foo = mock('foo') foo.expects(:bar).with(1).returns('a') - foo.stubs(:bar).with(any_parameters).returns('b').then.returns('c') + foo.stubs(:bar).with(any_parameters).multiple_yields(%w[b c], %w[d e]).returns('f').raises(StandardError) - 2.times { foo.bar(2, 3) } - foo.bar(3, 2) + foo.bar(1, 2) { |_ignored| } + assert_raise(StandardError) { foo.bar(3, 4) { |_ignored| } } end assert_failed(test_result) assert_equal [ @@ -93,10 +51,9 @@ def test_should_display_arguments_of_matching_invocations 'unsatisfied expectations:', '- expected exactly once, not yet invoked: #.bar(1)', 'satisfied expectations:', - '- allowed any number of times, invoked 3 times: #.bar(any_parameters)', - ' - #.bar(2, 3) # => "b"', - ' - #.bar(2, 3) # => "c"', - ' - #.bar(3, 2) # => "c"' + '- allowed any number of times, invoked twice: #.bar(any_parameters)', + ' - #.bar(1, 2) # => "f" after yielding ("b", "c"), then ("d", "e")', + ' - #.bar(3, 4) # => raised StandardError after yielding ("b", "c"), then ("d", "e")' ], test_result.failure_message_lines end end From 4b133ecfda2c9456bb9e3405024719a4135c50d3 Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Thu, 24 Oct 2019 17:44:20 +0100 Subject: [PATCH 08/28] Replace invocation_count with invocations.size Less state to maintain. Array#size is O(1), so should be fine performance-wise. --- lib/mocha/expectation.rb | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/mocha/expectation.rb b/lib/mocha/expectation.rb index cec6f89c8..6518646f6 100644 --- a/lib/mocha/expectation.rb +++ b/lib/mocha/expectation.rb @@ -513,7 +513,6 @@ def initialize(mock, expected_method_name, backtrace = nil) @ordering_constraints = [] @side_effects = [] @cardinality = Cardinality.exactly(1) - @invocation_count = 0 @return_values = ReturnValues.new @yield_parameters = YieldParameters.new @backtrace = backtrace || caller @@ -557,17 +556,16 @@ def match?(actual_method_name, *actual_parameters) # @private def invocations_allowed? - @cardinality.invocations_allowed?(@invocation_count) + @cardinality.invocations_allowed?(@invocations.size) end # @private def satisfied? - @cardinality.satisfied?(@invocation_count) + @cardinality.satisfied?(@invocations.size) end # @private def invoke(*arguments) - @invocation_count += 1 perform_side_effects invocation = Invocation.new(method_name, @yield_parameters, @return_values) @cardinality << invocation @@ -577,12 +575,12 @@ def invoke(*arguments) # @private def verified?(assertion_counter = nil) assertion_counter.increment if assertion_counter && @cardinality.needs_verifying? - @cardinality.verified?(@invocation_count) + @cardinality.verified?(@invocations.size) end # @private def used? - @cardinality.used?(@invocation_count) + @cardinality.used?(@invocations.size) end # @private @@ -595,11 +593,11 @@ def inspect # @private def mocha_inspect message = "#{@cardinality.mocha_inspect}, " - message << case @invocation_count + message << case @invocations.size when 0 then 'not yet invoked' when 1 then 'invoked once' when 2 then 'invoked twice' - else "invoked #{@invocation_count} times" + else "invoked #{@invocations.size} times" end message << ': ' message << method_signature From 90c57c33668ca7850b13024a287f3d9e3e30c52c Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Thu, 24 Oct 2019 18:22:51 +0100 Subject: [PATCH 09/28] add test for yielding & returning nothing --- ...invocations_alongside_expectations_test.rb | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/test/acceptance/display_matching_invocations_alongside_expectations_test.rb b/test/acceptance/display_matching_invocations_alongside_expectations_test.rb index 0b232f3c2..02cd38bc7 100644 --- a/test/acceptance/display_matching_invocations_alongside_expectations_test.rb +++ b/test/acceptance/display_matching_invocations_alongside_expectations_test.rb @@ -15,7 +15,7 @@ def teardown teardown_acceptance_test end - def test_should_display_results_of_matching_invocations_alongside_expectations_in_verbose_mode + def test_should_display_results test_result = run_as_test do foo = mock('foo') foo.expects(:bar).with(1).returns('a') @@ -36,7 +36,7 @@ def test_should_display_results_of_matching_invocations_alongside_expectations_i ], test_result.failure_message_lines end - def test_should_display_yields_of_matching_invocations_alongside_expectations_in_verbose_mode + def test_should_display_yields test_result = run_as_test do foo = mock('foo') foo.expects(:bar).with(1).returns('a') @@ -56,4 +56,23 @@ def test_should_display_yields_of_matching_invocations_alongside_expectations_in ' - #.bar(3, 4) # => raised StandardError after yielding ("b", "c"), then ("d", "e")' ], test_result.failure_message_lines end + + def test_should_display_empty_yield_and_return + test_result = run_as_test do + foo = mock('foo') + foo.expects(:bar).with(1).returns('a') + foo.stubs(:bar).with(any_parameters).yields + + foo.bar(1, 2) { |_ignored| } + end + assert_failed(test_result) + assert_equal [ + 'not all expectations were satisfied', + 'unsatisfied expectations:', + '- expected exactly once, not yet invoked: #.bar(1)', + 'satisfied expectations:', + '- allowed any number of times, invoked once: #.bar(any_parameters)', + ' - #.bar(1, 2) # => nil after yielding ()' + ], test_result.failure_message_lines + end end From 26e43976af18a5d6a7abb052cffb3fdf025e33fc Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Thu, 24 Oct 2019 19:27:58 +0100 Subject: [PATCH 10/28] DRY up display matching invocations alongside expectations test extract the assert invocations method. Note that the method is a bit fragile as it assumes a certain fixture, without which the calling test would fail. --- ...invocations_alongside_expectations_test.rb | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/test/acceptance/display_matching_invocations_alongside_expectations_test.rb b/test/acceptance/display_matching_invocations_alongside_expectations_test.rb index 02cd38bc7..2987a9dbf 100644 --- a/test/acceptance/display_matching_invocations_alongside_expectations_test.rb +++ b/test/acceptance/display_matching_invocations_alongside_expectations_test.rb @@ -24,16 +24,12 @@ def test_should_display_results foo.bar(1, 2) { |_ignored| } assert_raise(StandardError) { foo.bar(3, 4) { |_ignored| } } end - assert_failed(test_result) - assert_equal [ - 'not all expectations were satisfied', - 'unsatisfied expectations:', - '- expected exactly once, not yet invoked: #.bar(1)', - 'satisfied expectations:', + assert_invocations( + test_result, '- allowed any number of times, invoked twice: #.bar(any_parameters)', ' - #.bar(1, 2) # => "f"', ' - #.bar(3, 4) # => raised StandardError' - ], test_result.failure_message_lines + ) end def test_should_display_yields @@ -45,16 +41,12 @@ def test_should_display_yields foo.bar(1, 2) { |_ignored| } assert_raise(StandardError) { foo.bar(3, 4) { |_ignored| } } end - assert_failed(test_result) - assert_equal [ - 'not all expectations were satisfied', - 'unsatisfied expectations:', - '- expected exactly once, not yet invoked: #.bar(1)', - 'satisfied expectations:', + assert_invocations( + test_result, '- allowed any number of times, invoked twice: #.bar(any_parameters)', ' - #.bar(1, 2) # => "f" after yielding ("b", "c"), then ("d", "e")', ' - #.bar(3, 4) # => raised StandardError after yielding ("b", "c"), then ("d", "e")' - ], test_result.failure_message_lines + ) end def test_should_display_empty_yield_and_return @@ -65,14 +57,21 @@ def test_should_display_empty_yield_and_return foo.bar(1, 2) { |_ignored| } end + assert_invocations( + test_result, + '- allowed any number of times, invoked once: #.bar(any_parameters)', + ' - #.bar(1, 2) # => nil after yielding ()' + ) + end + + def assert_invocations(test_result, *invocations) assert_failed(test_result) assert_equal [ 'not all expectations were satisfied', 'unsatisfied expectations:', '- expected exactly once, not yet invoked: #.bar(1)', 'satisfied expectations:', - '- allowed any number of times, invoked once: #.bar(any_parameters)', - ' - #.bar(1, 2) # => nil after yielding ()' + *invocations ], test_result.failure_message_lines end end From 78cfb20f26ec70adf1ae9e92deb06a0f9f84faef Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Wed, 30 Oct 2019 19:29:48 +0000 Subject: [PATCH 11/28] Prep to move invocation size based logic to Cardinality Cardinality and invocations go hand-in-hand. So, the target is to move invocations to Cardinality. --- lib/mocha/expectation.rb | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/mocha/expectation.rb b/lib/mocha/expectation.rb index 6518646f6..760721fa3 100644 --- a/lib/mocha/expectation.rb +++ b/lib/mocha/expectation.rb @@ -593,12 +593,7 @@ def inspect # @private def mocha_inspect message = "#{@cardinality.mocha_inspect}, " - message << case @invocations.size - when 0 then 'not yet invoked' - when 1 then 'invoked once' - when 2 then 'invoked twice' - else "invoked #{@invocations.size} times" - end + message << actual_invocations(@invocations.size) message << ': ' message << method_signature message << "; #{@ordering_constraints.map(&:mocha_inspect).join('; ')}" unless @ordering_constraints.empty? @@ -613,6 +608,19 @@ def method_signature private + def actual_invocations(invocation_count) + case invocation_count + when 0 then + 'not yet invoked' + when 1 then + 'invoked once' + when 2 then + 'invoked twice' + else + "invoked #{invocation_count} times" + end + end + def method_name "#{@mock.mocha_inspect}.#{@method_matcher.mocha_inspect}" end From 41142f7452b312e1dddc52b9b99dc96672409ca7 Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Wed, 30 Oct 2019 19:34:40 +0000 Subject: [PATCH 12/28] Move invocation size based logic to Cardinality Cardinality and invocations go hand-in-hand. So, the target is to move invocations to Cardinality. --- lib/mocha/cardinality.rb | 13 +++++++++++++ lib/mocha/expectation.rb | 15 +-------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/mocha/cardinality.rb b/lib/mocha/cardinality.rb index 6cc00684d..64ad07792 100644 --- a/lib/mocha/cardinality.rb +++ b/lib/mocha/cardinality.rb @@ -68,6 +68,19 @@ def mocha_inspect end end + def actual_invocations(invocation_count) + case invocation_count + when 0 then + 'not yet invoked' + when 1 then + 'invoked once' + when 2 then + 'invoked twice' + else + "invoked #{invocation_count} times" + end + end + protected attr_reader :required, :maximum diff --git a/lib/mocha/expectation.rb b/lib/mocha/expectation.rb index 760721fa3..c13e79a72 100644 --- a/lib/mocha/expectation.rb +++ b/lib/mocha/expectation.rb @@ -593,7 +593,7 @@ def inspect # @private def mocha_inspect message = "#{@cardinality.mocha_inspect}, " - message << actual_invocations(@invocations.size) + message << @cardinality.actual_invocations(@invocations.size) message << ': ' message << method_signature message << "; #{@ordering_constraints.map(&:mocha_inspect).join('; ')}" unless @ordering_constraints.empty? @@ -608,19 +608,6 @@ def method_signature private - def actual_invocations(invocation_count) - case invocation_count - when 0 then - 'not yet invoked' - when 1 then - 'invoked once' - when 2 then - 'invoked twice' - else - "invoked #{invocation_count} times" - end - end - def method_name "#{@mock.mocha_inspect}.#{@method_matcher.mocha_inspect}" end From b8e07cf0e52f5a98a36ad3adca3abb6be9b2ac0f Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Wed, 30 Oct 2019 19:46:11 +0000 Subject: [PATCH 13/28] Move invocations list to Cardinality The target is to encapsulate it inside Cardinality in a tell-don't-ask style. --- lib/mocha/cardinality.rb | 7 +++++++ lib/mocha/expectation.rb | 13 ++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/mocha/cardinality.rb b/lib/mocha/cardinality.rb index 64ad07792..ebe3cdd85 100644 --- a/lib/mocha/cardinality.rb +++ b/lib/mocha/cardinality.rb @@ -23,9 +23,16 @@ def times(range_or_count) end end + attr_reader :invocations + def initialize(required, maximum) @required = required @maximum = maximum + @invocations = [] + end + + def <<(invocation) + @invocations << invocation end def invocations_allowed?(invocation_count) diff --git a/lib/mocha/expectation.rb b/lib/mocha/expectation.rb index c13e79a72..764849bb2 100644 --- a/lib/mocha/expectation.rb +++ b/lib/mocha/expectation.rb @@ -516,7 +516,6 @@ def initialize(mock, expected_method_name, backtrace = nil) @return_values = ReturnValues.new @yield_parameters = YieldParameters.new @backtrace = backtrace || caller - @invocations = [] end # @private @@ -556,12 +555,12 @@ def match?(actual_method_name, *actual_parameters) # @private def invocations_allowed? - @cardinality.invocations_allowed?(@invocations.size) + @cardinality.invocations_allowed?(@cardinality.invocations.size) end # @private def satisfied? - @cardinality.satisfied?(@invocations.size) + @cardinality.satisfied?(@cardinality.invocations.size) end # @private @@ -575,12 +574,12 @@ def invoke(*arguments) # @private def verified?(assertion_counter = nil) assertion_counter.increment if assertion_counter && @cardinality.needs_verifying? - @cardinality.verified?(@invocations.size) + @cardinality.verified?(@cardinality.invocations.size) end # @private def used? - @cardinality.used?(@invocations.size) + @cardinality.used?(@cardinality.invocations.size) end # @private @@ -593,7 +592,7 @@ def inspect # @private def mocha_inspect message = "#{@cardinality.mocha_inspect}, " - message << @cardinality.actual_invocations(@invocations.size) + message << @cardinality.actual_invocations(@cardinality.invocations.size) message << ': ' message << method_signature message << "; #{@ordering_constraints.map(&:mocha_inspect).join('; ')}" unless @ordering_constraints.empty? @@ -613,7 +612,7 @@ def method_name end def invocations - @invocations.map(&:mocha_inspect).join + @cardinality.invocations.map(&:mocha_inspect).join end end end From e7a9618ca6c4648aff5cf863ff938e66eb4a0f3d Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Wed, 30 Oct 2019 20:03:39 +0000 Subject: [PATCH 14/28] Infer invocation count from invocations size The target is to encapsulate it inside Cardinality in a tell-don't-ask style. --- lib/mocha/cardinality.rb | 22 +++++++++++----------- lib/mocha/expectation.rb | 10 +++++----- test/unit/cardinality_test.rb | 28 ++++++++++++++++++++-------- 3 files changed, 36 insertions(+), 24 deletions(-) diff --git a/lib/mocha/cardinality.rb b/lib/mocha/cardinality.rb index ebe3cdd85..4ad879e24 100644 --- a/lib/mocha/cardinality.rb +++ b/lib/mocha/cardinality.rb @@ -35,28 +35,28 @@ def <<(invocation) @invocations << invocation end - def invocations_allowed?(invocation_count) - invocation_count < maximum + def invocations_allowed? + @invocations.size < maximum end - def satisfied?(invocations_so_far) - invocations_so_far >= required + def satisfied? + @invocations.size >= required end def needs_verifying? !allowed_any_number_of_times? end - def verified?(invocation_count) - (invocation_count >= required) && (invocation_count <= maximum) + def verified? + (@invocations.size >= required) && (@invocations.size <= maximum) end def allowed_any_number_of_times? required.zero? && infinite?(maximum) end - def used?(invocation_count) - (invocation_count > 0) || maximum.zero? + def used? + (@invocations.size > 0) || maximum.zero? end def mocha_inspect @@ -75,8 +75,8 @@ def mocha_inspect end end - def actual_invocations(invocation_count) - case invocation_count + def actual_invocations + case @invocations.size when 0 then 'not yet invoked' when 1 then @@ -84,7 +84,7 @@ def actual_invocations(invocation_count) when 2 then 'invoked twice' else - "invoked #{invocation_count} times" + "invoked #{@invocations.size} times" end end diff --git a/lib/mocha/expectation.rb b/lib/mocha/expectation.rb index 764849bb2..e5ddcd880 100644 --- a/lib/mocha/expectation.rb +++ b/lib/mocha/expectation.rb @@ -555,12 +555,12 @@ def match?(actual_method_name, *actual_parameters) # @private def invocations_allowed? - @cardinality.invocations_allowed?(@cardinality.invocations.size) + @cardinality.invocations_allowed? end # @private def satisfied? - @cardinality.satisfied?(@cardinality.invocations.size) + @cardinality.satisfied? end # @private @@ -574,12 +574,12 @@ def invoke(*arguments) # @private def verified?(assertion_counter = nil) assertion_counter.increment if assertion_counter && @cardinality.needs_verifying? - @cardinality.verified?(@cardinality.invocations.size) + @cardinality.verified? end # @private def used? - @cardinality.used?(@cardinality.invocations.size) + @cardinality.used? end # @private @@ -592,7 +592,7 @@ def inspect # @private def mocha_inspect message = "#{@cardinality.mocha_inspect}, " - message << @cardinality.actual_invocations(@cardinality.invocations.size) + message << @cardinality.actual_invocations message << ': ' message << method_signature message << "; #{@ordering_constraints.map(&:mocha_inspect).join('; ')}" unless @ordering_constraints.empty? diff --git a/test/unit/cardinality_test.rb b/test/unit/cardinality_test.rb index ee62c8014..ddda425e6 100644 --- a/test/unit/cardinality_test.rb +++ b/test/unit/cardinality_test.rb @@ -1,23 +1,35 @@ require File.expand_path('../../test_helper', __FILE__) require 'mocha/cardinality' +require 'mocha/return_values' +require 'mocha/yield_parameters' class CardinalityTest < Mocha::TestCase include Mocha + def new_invocation + Invocation.new(:foo, YieldParameters.new, ReturnValues.new) + end + def test_should_allow_invocations_if_invocation_count_has_not_yet_reached_maximum cardinality = Cardinality.new(2, 3) - assert cardinality.invocations_allowed?(0) - assert cardinality.invocations_allowed?(1) - assert cardinality.invocations_allowed?(2) - assert !cardinality.invocations_allowed?(3) + assert cardinality.invocations_allowed? + cardinality << new_invocation + assert cardinality.invocations_allowed? + cardinality << new_invocation + assert cardinality.invocations_allowed? + cardinality << new_invocation + assert !cardinality.invocations_allowed? end def test_should_be_satisfied_if_invocations_so_far_have_reached_required_threshold cardinality = Cardinality.new(2, 3) - assert !cardinality.satisfied?(0) - assert !cardinality.satisfied?(1) - assert cardinality.satisfied?(2) - assert cardinality.satisfied?(3) + assert !cardinality.satisfied? + cardinality << new_invocation + assert !cardinality.satisfied? + cardinality << new_invocation + assert cardinality.satisfied? + cardinality << new_invocation + assert cardinality.satisfied? end def test_should_describe_cardinality From 2f7011ad4f31e061e236a898657be6c521619f44 Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Wed, 30 Oct 2019 20:06:52 +0000 Subject: [PATCH 15/28] Encapsulate invocations inside Cardinality --- lib/mocha/cardinality.rb | 6 ++++-- lib/mocha/expectation.rb | 6 +----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/mocha/cardinality.rb b/lib/mocha/cardinality.rb index 4ad879e24..6e23a77dd 100644 --- a/lib/mocha/cardinality.rb +++ b/lib/mocha/cardinality.rb @@ -23,8 +23,6 @@ def times(range_or_count) end end - attr_reader :invocations - def initialize(required, maximum) @required = required @maximum = maximum @@ -88,6 +86,10 @@ def actual_invocations end end + def invocations + @invocations.map(&:mocha_inspect).join + end + protected attr_reader :required, :maximum diff --git a/lib/mocha/expectation.rb b/lib/mocha/expectation.rb index e5ddcd880..5a72b3240 100644 --- a/lib/mocha/expectation.rb +++ b/lib/mocha/expectation.rb @@ -596,7 +596,7 @@ def mocha_inspect message << ': ' message << method_signature message << "; #{@ordering_constraints.map(&:mocha_inspect).join('; ')}" unless @ordering_constraints.empty? - message << invocations if (ENV['MOCHA_OPTIONS'] || '').split(',').include?('verbose') + message << @cardinality.invocations if (ENV['MOCHA_OPTIONS'] || '').split(',').include?('verbose') message end @@ -610,9 +610,5 @@ def method_signature def method_name "#{@mock.mocha_inspect}.#{@method_matcher.mocha_inspect}" end - - def invocations - @cardinality.invocations.map(&:mocha_inspect).join - end end end From aa5ed0ecf4e80723ebbb2c6725d71530438c6c4a Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Wed, 30 Oct 2019 20:10:49 +0000 Subject: [PATCH 16/28] rename methods to better describe intention --- lib/mocha/cardinality.rb | 4 ++-- lib/mocha/expectation.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/mocha/cardinality.rb b/lib/mocha/cardinality.rb index 6e23a77dd..c73d66d6e 100644 --- a/lib/mocha/cardinality.rb +++ b/lib/mocha/cardinality.rb @@ -73,7 +73,7 @@ def mocha_inspect end end - def actual_invocations + def actual_times case @invocations.size when 0 then 'not yet invoked' @@ -86,7 +86,7 @@ def actual_invocations end end - def invocations + def actual_invocations @invocations.map(&:mocha_inspect).join end diff --git a/lib/mocha/expectation.rb b/lib/mocha/expectation.rb index 5a72b3240..389be7b1a 100644 --- a/lib/mocha/expectation.rb +++ b/lib/mocha/expectation.rb @@ -592,11 +592,11 @@ def inspect # @private def mocha_inspect message = "#{@cardinality.mocha_inspect}, " - message << @cardinality.actual_invocations + message << @cardinality.actual_times message << ': ' message << method_signature message << "; #{@ordering_constraints.map(&:mocha_inspect).join('; ')}" unless @ordering_constraints.empty? - message << @cardinality.invocations if (ENV['MOCHA_OPTIONS'] || '').split(',').include?('verbose') + message << @cardinality.actual_invocations if (ENV['MOCHA_OPTIONS'] || '').split(',').include?('verbose') message end From 1aa23bd418c69a8b0b88135d4c6dc564b361cf37 Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Wed, 30 Oct 2019 20:14:26 +0000 Subject: [PATCH 17/28] rename methods to better describe intention --- lib/mocha/cardinality.rb | 2 +- lib/mocha/expectation.rb | 2 +- test/unit/cardinality_test.rb | 26 +++++++++++++------------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/mocha/cardinality.rb b/lib/mocha/cardinality.rb index c73d66d6e..f9167d882 100644 --- a/lib/mocha/cardinality.rb +++ b/lib/mocha/cardinality.rb @@ -57,7 +57,7 @@ def used? (@invocations.size > 0) || maximum.zero? end - def mocha_inspect + def expected_times if allowed_any_number_of_times? 'allowed any number of times' elsif required.zero? && maximum.zero? diff --git a/lib/mocha/expectation.rb b/lib/mocha/expectation.rb index 389be7b1a..6f5e17e95 100644 --- a/lib/mocha/expectation.rb +++ b/lib/mocha/expectation.rb @@ -591,7 +591,7 @@ def inspect # @private def mocha_inspect - message = "#{@cardinality.mocha_inspect}, " + message = "#{@cardinality.expected_times}, " message << @cardinality.actual_times message << ': ' message << method_signature diff --git a/test/unit/cardinality_test.rb b/test/unit/cardinality_test.rb index ddda425e6..4f3f17cc3 100644 --- a/test/unit/cardinality_test.rb +++ b/test/unit/cardinality_test.rb @@ -33,23 +33,23 @@ def test_should_be_satisfied_if_invocations_so_far_have_reached_required_thresho end def test_should_describe_cardinality - assert_equal 'allowed any number of times', Cardinality.at_least(0).mocha_inspect + assert_equal 'allowed any number of times', Cardinality.at_least(0).expected_times - assert_equal 'expected at most once', Cardinality.at_most(1).mocha_inspect - assert_equal 'expected at most twice', Cardinality.at_most(2).mocha_inspect - assert_equal 'expected at most 3 times', Cardinality.at_most(3).mocha_inspect + assert_equal 'expected at most once', Cardinality.at_most(1).expected_times + assert_equal 'expected at most twice', Cardinality.at_most(2).expected_times + assert_equal 'expected at most 3 times', Cardinality.at_most(3).expected_times - assert_equal 'expected at least once', Cardinality.at_least(1).mocha_inspect - assert_equal 'expected at least twice', Cardinality.at_least(2).mocha_inspect - assert_equal 'expected at least 3 times', Cardinality.at_least(3).mocha_inspect + assert_equal 'expected at least once', Cardinality.at_least(1).expected_times + assert_equal 'expected at least twice', Cardinality.at_least(2).expected_times + assert_equal 'expected at least 3 times', Cardinality.at_least(3).expected_times - assert_equal 'expected never', Cardinality.exactly(0).mocha_inspect - assert_equal 'expected exactly once', Cardinality.exactly(1).mocha_inspect - assert_equal 'expected exactly twice', Cardinality.exactly(2).mocha_inspect - assert_equal 'expected exactly 3 times', Cardinality.times(3).mocha_inspect + assert_equal 'expected never', Cardinality.exactly(0).expected_times + assert_equal 'expected exactly once', Cardinality.exactly(1).expected_times + assert_equal 'expected exactly twice', Cardinality.exactly(2).expected_times + assert_equal 'expected exactly 3 times', Cardinality.times(3).expected_times - assert_equal 'expected between 2 and 4 times', Cardinality.times(2..4).mocha_inspect - assert_equal 'expected between 1 and 3 times', Cardinality.times(1..3).mocha_inspect + assert_equal 'expected between 2 and 4 times', Cardinality.times(2..4).expected_times + assert_equal 'expected between 1 and 3 times', Cardinality.times(1..3).expected_times end def test_should_need_verifying From f1922d4707691d035b8c9e9a70ad2923af9398ab Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Wed, 30 Oct 2019 20:18:29 +0000 Subject: [PATCH 18/28] rename methods to better describe intention expected hadn't quite sounded right because it also included allowed --- lib/mocha/cardinality.rb | 4 ++-- lib/mocha/expectation.rb | 4 ++-- test/unit/cardinality_test.rb | 26 +++++++++++++------------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/mocha/cardinality.rb b/lib/mocha/cardinality.rb index f9167d882..25b00ec45 100644 --- a/lib/mocha/cardinality.rb +++ b/lib/mocha/cardinality.rb @@ -57,7 +57,7 @@ def used? (@invocations.size > 0) || maximum.zero? end - def expected_times + def anticipated_times if allowed_any_number_of_times? 'allowed any number of times' elsif required.zero? && maximum.zero? @@ -73,7 +73,7 @@ def expected_times end end - def actual_times + def invoked_times case @invocations.size when 0 then 'not yet invoked' diff --git a/lib/mocha/expectation.rb b/lib/mocha/expectation.rb index 6f5e17e95..43d4bfe22 100644 --- a/lib/mocha/expectation.rb +++ b/lib/mocha/expectation.rb @@ -591,8 +591,8 @@ def inspect # @private def mocha_inspect - message = "#{@cardinality.expected_times}, " - message << @cardinality.actual_times + message = "#{@cardinality.anticipated_times}, " + message << @cardinality.invoked_times message << ': ' message << method_signature message << "; #{@ordering_constraints.map(&:mocha_inspect).join('; ')}" unless @ordering_constraints.empty? diff --git a/test/unit/cardinality_test.rb b/test/unit/cardinality_test.rb index 4f3f17cc3..0d92a9434 100644 --- a/test/unit/cardinality_test.rb +++ b/test/unit/cardinality_test.rb @@ -33,23 +33,23 @@ def test_should_be_satisfied_if_invocations_so_far_have_reached_required_thresho end def test_should_describe_cardinality - assert_equal 'allowed any number of times', Cardinality.at_least(0).expected_times + assert_equal 'allowed any number of times', Cardinality.at_least(0).anticipated_times - assert_equal 'expected at most once', Cardinality.at_most(1).expected_times - assert_equal 'expected at most twice', Cardinality.at_most(2).expected_times - assert_equal 'expected at most 3 times', Cardinality.at_most(3).expected_times + assert_equal 'expected at most once', Cardinality.at_most(1).anticipated_times + assert_equal 'expected at most twice', Cardinality.at_most(2).anticipated_times + assert_equal 'expected at most 3 times', Cardinality.at_most(3).anticipated_times - assert_equal 'expected at least once', Cardinality.at_least(1).expected_times - assert_equal 'expected at least twice', Cardinality.at_least(2).expected_times - assert_equal 'expected at least 3 times', Cardinality.at_least(3).expected_times + assert_equal 'expected at least once', Cardinality.at_least(1).anticipated_times + assert_equal 'expected at least twice', Cardinality.at_least(2).anticipated_times + assert_equal 'expected at least 3 times', Cardinality.at_least(3).anticipated_times - assert_equal 'expected never', Cardinality.exactly(0).expected_times - assert_equal 'expected exactly once', Cardinality.exactly(1).expected_times - assert_equal 'expected exactly twice', Cardinality.exactly(2).expected_times - assert_equal 'expected exactly 3 times', Cardinality.times(3).expected_times + assert_equal 'expected never', Cardinality.exactly(0).anticipated_times + assert_equal 'expected exactly once', Cardinality.exactly(1).anticipated_times + assert_equal 'expected exactly twice', Cardinality.exactly(2).anticipated_times + assert_equal 'expected exactly 3 times', Cardinality.times(3).anticipated_times - assert_equal 'expected between 2 and 4 times', Cardinality.times(2..4).expected_times - assert_equal 'expected between 1 and 3 times', Cardinality.times(1..3).expected_times + assert_equal 'expected between 2 and 4 times', Cardinality.times(2..4).anticipated_times + assert_equal 'expected between 1 and 3 times', Cardinality.times(1..3).anticipated_times end def test_should_need_verifying From 77aa25e6ffca1495fa2f5c61e56756c413a20264 Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Wed, 30 Oct 2019 20:21:20 +0000 Subject: [PATCH 19/28] interpolate rather than append simple expressions --- lib/mocha/expectation.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/mocha/expectation.rb b/lib/mocha/expectation.rb index 43d4bfe22..4256c62fa 100644 --- a/lib/mocha/expectation.rb +++ b/lib/mocha/expectation.rb @@ -591,10 +591,7 @@ def inspect # @private def mocha_inspect - message = "#{@cardinality.anticipated_times}, " - message << @cardinality.invoked_times - message << ': ' - message << method_signature + message = "#{@cardinality.anticipated_times}, #{@cardinality.invoked_times}: #{method_signature}" message << "; #{@ordering_constraints.map(&:mocha_inspect).join('; ')}" unless @ordering_constraints.empty? message << @cardinality.actual_invocations if (ENV['MOCHA_OPTIONS'] || '').split(',').include?('verbose') message From fd290eaa07dc381d02a3a854353570aa08def8e2 Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Wed, 30 Oct 2019 20:32:21 +0000 Subject: [PATCH 20/28] use same times logic for non-zero configured & invoked --- lib/mocha/cardinality.rb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/mocha/cardinality.rb b/lib/mocha/cardinality.rb index 25b00ec45..f5504f4d2 100644 --- a/lib/mocha/cardinality.rb +++ b/lib/mocha/cardinality.rb @@ -77,12 +77,8 @@ def invoked_times case @invocations.size when 0 then 'not yet invoked' - when 1 then - 'invoked once' - when 2 then - 'invoked twice' else - "invoked #{@invocations.size} times" + "invoked #{times(@invocations.size)}" end end From d5c7e5b92489331d53c141e6748e776a4ad7372f Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Wed, 30 Oct 2019 20:38:27 +0000 Subject: [PATCH 21/28] times is never invoked with 0 --- lib/mocha/cardinality.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/mocha/cardinality.rb b/lib/mocha/cardinality.rb index f5504f4d2..678ce6ab9 100644 --- a/lib/mocha/cardinality.rb +++ b/lib/mocha/cardinality.rb @@ -92,7 +92,6 @@ def actual_invocations def times(number) case number - when 0 then 'no times' when 1 then 'once' when 2 then 'twice' else "#{number} times" From 7051809885a4b761f60bc0e4711c0f76e0da106f Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Wed, 30 Oct 2019 20:41:26 +0000 Subject: [PATCH 22/28] more expressive & concise conditionals & booleans --- lib/mocha/cardinality.rb | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/mocha/cardinality.rb b/lib/mocha/cardinality.rb index 678ce6ab9..4df969f37 100644 --- a/lib/mocha/cardinality.rb +++ b/lib/mocha/cardinality.rb @@ -54,7 +54,7 @@ def allowed_any_number_of_times? end def used? - (@invocations.size > 0) || maximum.zero? + @invocations.any? || maximum.zero? end def anticipated_times @@ -74,12 +74,7 @@ def anticipated_times end def invoked_times - case @invocations.size - when 0 then - 'not yet invoked' - else - "invoked #{times(@invocations.size)}" - end + @invocations.none? ? 'not yet invoked' : "invoked #{times(@invocations.size)}" end def actual_invocations From 21ac1f9013d397e8a95b7db2fae32bbdca046139 Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Wed, 30 Oct 2019 21:24:38 +0000 Subject: [PATCH 23/28] use consistent phrasing for configured & invoked times --- lib/mocha/cardinality.rb | 5 +++-- ...play_matching_invocations_alongside_expectations_test.rb | 2 +- test/acceptance/exception_rescue_test.rb | 2 +- test/acceptance/expected_invocation_count_test.rb | 4 ++-- .../stub_any_instance_method_defined_on_superclass_test.rb | 2 +- .../stub_class_method_defined_on_superclass_test.rb | 2 +- test/integration/shared_tests.rb | 6 +++--- test/unit/expectation_test.rb | 2 +- 8 files changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/mocha/cardinality.rb b/lib/mocha/cardinality.rb index 4df969f37..6a69e662c 100644 --- a/lib/mocha/cardinality.rb +++ b/lib/mocha/cardinality.rb @@ -61,7 +61,7 @@ def anticipated_times if allowed_any_number_of_times? 'allowed any number of times' elsif required.zero? && maximum.zero? - 'expected never' + "expected #{times(maximum)}" elsif required == maximum "expected exactly #{times(required)}" elsif infinite?(maximum) @@ -74,7 +74,7 @@ def anticipated_times end def invoked_times - @invocations.none? ? 'not yet invoked' : "invoked #{times(@invocations.size)}" + "invoked #{times(@invocations.size)}" end def actual_invocations @@ -87,6 +87,7 @@ def actual_invocations def times(number) case number + when 0 then 'never' when 1 then 'once' when 2 then 'twice' else "#{number} times" diff --git a/test/acceptance/display_matching_invocations_alongside_expectations_test.rb b/test/acceptance/display_matching_invocations_alongside_expectations_test.rb index 2987a9dbf..fd0676a94 100644 --- a/test/acceptance/display_matching_invocations_alongside_expectations_test.rb +++ b/test/acceptance/display_matching_invocations_alongside_expectations_test.rb @@ -69,7 +69,7 @@ def assert_invocations(test_result, *invocations) assert_equal [ 'not all expectations were satisfied', 'unsatisfied expectations:', - '- expected exactly once, not yet invoked: #.bar(1)', + '- expected exactly once, invoked never: #.bar(1)', 'satisfied expectations:', *invocations ], test_result.failure_message_lines diff --git a/test/acceptance/exception_rescue_test.rb b/test/acceptance/exception_rescue_test.rb index e8f4a5608..54905503a 100644 --- a/test/acceptance/exception_rescue_test.rb +++ b/test/acceptance/exception_rescue_test.rb @@ -48,7 +48,7 @@ def test_unsatisfied_expectation_exception_is_not_caught_by_standard_rescue assert_equal [ 'not all expectations were satisfied', 'unsatisfied expectations:', - '- expected exactly once, not yet invoked: #.some_method(any_parameters)' + '- expected exactly once, invoked never: #.some_method(any_parameters)' ], test_result.failure_message_lines end end diff --git a/test/acceptance/expected_invocation_count_test.rb b/test/acceptance/expected_invocation_count_test.rb index fc177709a..9067df6a6 100644 --- a/test/acceptance/expected_invocation_count_test.rb +++ b/test/acceptance/expected_invocation_count_test.rb @@ -155,7 +155,7 @@ def test_should_fail_if_method_is_expected_at_least_once_but_is_never_called assert_equal [ 'not all expectations were satisfied', 'unsatisfied expectations:', - '- expected at least once, not yet invoked: #.method(any_parameters)' + '- expected at least once, invoked never: #.method(any_parameters)' ], test_result.failure_message_lines end @@ -224,7 +224,7 @@ def test_should_fail_fast_if_there_is_no_matching_expectation assert_equal [ 'unexpected invocation: #.method()', 'unsatisfied expectations:', - '- expected exactly once, not yet invoked: #.method(1)' + '- expected exactly once, invoked never: #.method(1)' ], test_result.failure_message_lines end end diff --git a/test/acceptance/stub_any_instance_method_defined_on_superclass_test.rb b/test/acceptance/stub_any_instance_method_defined_on_superclass_test.rb index 27152c6d0..8087e1c11 100644 --- a/test/acceptance/stub_any_instance_method_defined_on_superclass_test.rb +++ b/test/acceptance/stub_any_instance_method_defined_on_superclass_test.rb @@ -59,7 +59,7 @@ def my_instance_method; end assert_equal [ 'not all expectations were satisfied', 'unsatisfied expectations:', - '- expected exactly once, not yet invoked: #.my_instance_method(any_parameters)' + '- expected exactly once, invoked never: #.my_instance_method(any_parameters)' ], test_result.failure_message_lines end end diff --git a/test/acceptance/stub_class_method_defined_on_superclass_test.rb b/test/acceptance/stub_class_method_defined_on_superclass_test.rb index 14797c65c..6b4a74f5f 100644 --- a/test/acceptance/stub_class_method_defined_on_superclass_test.rb +++ b/test/acceptance/stub_class_method_defined_on_superclass_test.rb @@ -138,7 +138,7 @@ def self.my_class_method; end assert_equal [ 'not all expectations were satisfied', 'unsatisfied expectations:', - '- expected exactly once, not yet invoked: superklass.my_class_method(any_parameters)' + '- expected exactly once, invoked never: superklass.my_class_method(any_parameters)' ], test_result.failure_message_lines end # rubocop:enable Lint/DuplicateMethods diff --git a/test/integration/shared_tests.rb b/test/integration/shared_tests.rb index 00ea88292..f5ef1886f 100644 --- a/test/integration/shared_tests.rb +++ b/test/integration/shared_tests.rb @@ -63,7 +63,7 @@ def test_mock_object_unsatisfied_expectation assert_equal [ 'not all expectations were satisfied', 'unsatisfied expectations:', - '- expected exactly once, not yet invoked: #.expected(any_parameters)' + '- expected exactly once, invoked never: #.expected(any_parameters)' ], test_result.failure_message_lines end @@ -101,7 +101,7 @@ def test_mock_object_unsatisfied_expectation_in_setup assert_equal [ 'not all expectations were satisfied', 'unsatisfied expectations:', - '- expected exactly once, not yet invoked: #.expected(any_parameters)' + '- expected exactly once, invoked never: #.expected(any_parameters)' ], test_result.failure_message_lines end @@ -151,7 +151,7 @@ def test_real_object_unsatisfied_expectation assert_equal [ 'not all expectations were satisfied', 'unsatisfied expectations:', - "- expected exactly once, not yet invoked: #{object.mocha_inspect}.expected(any_parameters)" + "- expected exactly once, invoked never: #{object.mocha_inspect}.expected(any_parameters)" ], test_result.failure_message_lines end diff --git a/test/unit/expectation_test.rb b/test/unit/expectation_test.rb index 3a37dcd7e..8363df14a 100644 --- a/test/unit/expectation_test.rb +++ b/test/unit/expectation_test.rb @@ -283,7 +283,7 @@ def test_should_verify_successfully_if_expected_call_was_made_at_least_once def test_should_not_verify_successfully_if_expected_call_was_not_made_at_least_once expectation = new_expectation.with(1, 2, 3).at_least_once assert !expectation.verified? - assert_match(/expected at least once, not yet invoked/i, expectation.mocha_inspect) + assert_match(/expected at least once, invoked never/i, expectation.mocha_inspect) end def test_should_verify_successfully_if_expected_call_was_made_expected_number_of_times From 0d57961fdbe709cf28df26484d6ce3a5c1d9c03f Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Mon, 4 Nov 2019 21:13:46 +0000 Subject: [PATCH 24/28] reduce test's knowledge of irrelevant details --- lib/mocha/invocation.rb | 4 +++- test/unit/cardinality_test.rb | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/mocha/invocation.rb b/lib/mocha/invocation.rb index 4481b43c8..9e1e11fca 100644 --- a/lib/mocha/invocation.rb +++ b/lib/mocha/invocation.rb @@ -1,10 +1,12 @@ require 'mocha/parameters_matcher' require 'mocha/raised_exception' +require 'mocha/return_values' +require 'mocha/yield_parameters' module Mocha class Invocation # @private - def initialize(method_name, yield_parameters, return_values) + def initialize(method_name, yield_parameters = YieldParameters.new, return_values = ReturnValues.new) @method_name = method_name @yield_parameters = yield_parameters @return_values = return_values diff --git a/test/unit/cardinality_test.rb b/test/unit/cardinality_test.rb index 0d92a9434..048115dec 100644 --- a/test/unit/cardinality_test.rb +++ b/test/unit/cardinality_test.rb @@ -1,5 +1,6 @@ require File.expand_path('../../test_helper', __FILE__) require 'mocha/cardinality' +require 'mocha/invocation' require 'mocha/return_values' require 'mocha/yield_parameters' @@ -7,7 +8,7 @@ class CardinalityTest < Mocha::TestCase include Mocha def new_invocation - Invocation.new(:foo, YieldParameters.new, ReturnValues.new) + Invocation.new(:irrelevant) end def test_should_allow_invocations_if_invocation_count_has_not_yet_reached_maximum From 9007a971c5dba39e43a30a16e8569782c39c656c Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Tue, 5 Nov 2019 10:47:45 +0000 Subject: [PATCH 25/28] record result of invocation before 'returning' it This helps us avoid rescuing an exception raised by code elsewhere in the Mocha code. Also, enables us to handle the missed scenario where Expectaction#throws is called, 'coz without it, we'd need a generic way to catch any thrown tag and that might be quite awkward. --- lib/mocha/exception_raiser.rb | 3 +- lib/mocha/invocation.rb | 20 +++++++----- lib/mocha/return_values.rb | 6 ++-- lib/mocha/single_return_value.rb | 3 +- lib/mocha/thrower.rb | 2 +- test/unit/exception_raiser_test.rb | 15 ++++++--- test/unit/return_values_test.rb | 45 +++++++++++++++------------ test/unit/single_return_value_test.rb | 7 ++++- test/unit/thrower_test.rb | 9 ++++-- 9 files changed, 68 insertions(+), 42 deletions(-) diff --git a/lib/mocha/exception_raiser.rb b/lib/mocha/exception_raiser.rb index 47f43a577..8b2430dfb 100644 --- a/lib/mocha/exception_raiser.rb +++ b/lib/mocha/exception_raiser.rb @@ -5,7 +5,8 @@ def initialize(exception, message) @message = message end - def evaluate + def evaluate(invocation) + invocation.raised(@exception) raise @exception, @exception.to_s if @exception.is_a?(Module) && (@exception < Interrupt) raise @exception, @message if @message raise @exception diff --git a/lib/mocha/invocation.rb b/lib/mocha/invocation.rb index 9e1e11fca..2cd19ab57 100644 --- a/lib/mocha/invocation.rb +++ b/lib/mocha/invocation.rb @@ -11,6 +11,7 @@ def initialize(method_name, yield_parameters = YieldParameters.new, return_value @yield_parameters = yield_parameters @return_values = return_values @yields = [] + @result = nil end # @private @@ -20,14 +21,17 @@ def call(*arguments) @yields << ParametersMatcher.new(yield_parameters) yield(*yield_parameters) end - begin - @result = @return_values.next - # rubocop:disable Lint/RescueException - rescue Exception => e - # rubocop:enable Lint/RescueException - @result = RaisedException.new(e) - raise - end + @return_values.next(self) + end + + # @private + def returned(value) + @result = value + end + + # @private + def raised(exception) + @result = RaisedException.new(exception) end # @private diff --git a/lib/mocha/return_values.rb b/lib/mocha/return_values.rb index c7a0263a8..8479e87a5 100644 --- a/lib/mocha/return_values.rb +++ b/lib/mocha/return_values.rb @@ -12,11 +12,11 @@ def initialize(*values) @values = values end - def next + def next(invocation) case @values.length when 0 then nil - when 1 then @values.first.evaluate - else @values.shift.evaluate + when 1 then @values.first.evaluate(invocation) + else @values.shift.evaluate(invocation) end end diff --git a/lib/mocha/single_return_value.rb b/lib/mocha/single_return_value.rb index 42535274e..b7145c79a 100644 --- a/lib/mocha/single_return_value.rb +++ b/lib/mocha/single_return_value.rb @@ -6,7 +6,8 @@ def initialize(value) @value = value end - def evaluate + def evaluate(invocation) + invocation.returned(@value) @value end end diff --git a/lib/mocha/thrower.rb b/lib/mocha/thrower.rb index c51e0ed00..0198e0c51 100644 --- a/lib/mocha/thrower.rb +++ b/lib/mocha/thrower.rb @@ -5,7 +5,7 @@ def initialize(tag, object = nil) @object = object end - def evaluate + def evaluate(_invocation) throw @tag, @object end end diff --git a/test/unit/exception_raiser_test.rb b/test/unit/exception_raiser_test.rb index bc481905f..a3f42314b 100644 --- a/test/unit/exception_raiser_test.rb +++ b/test/unit/exception_raiser_test.rb @@ -1,40 +1,45 @@ require File.expand_path('../../test_helper', __FILE__) +require 'mocha/invocation' require 'mocha/exception_raiser' require 'timeout' class ExceptionRaiserTest < Mocha::TestCase include Mocha + def new_invocation + Invocation.new(:irrelevant) + end + def test_should_raise_exception_with_specified_class_and_default_message exception_class = Class.new(StandardError) raiser = ExceptionRaiser.new(exception_class, nil) - exception = assert_raises(exception_class) { raiser.evaluate } + exception = assert_raises(exception_class) { raiser.evaluate(new_invocation) } assert_equal exception_class.to_s, exception.message end def test_should_raise_exception_with_specified_class_and_message exception_class = Class.new(StandardError) raiser = ExceptionRaiser.new(exception_class, 'message') - exception = assert_raises(exception_class) { raiser.evaluate } + exception = assert_raises(exception_class) { raiser.evaluate(new_invocation) } assert_equal 'message', exception.message end def test_should_raise_exception_instance exception_class = Class.new(StandardError) raiser = ExceptionRaiser.new(exception_class.new('message'), nil) - exception = assert_raises(exception_class) { raiser.evaluate } + exception = assert_raises(exception_class) { raiser.evaluate(new_invocation) } assert_equal 'message', exception.message end def test_should_raise_interrupt_exception_with_default_message_so_it_works_in_ruby_1_8_6 raiser = ExceptionRaiser.new(Interrupt, nil) - assert_raises(Interrupt) { raiser.evaluate } + assert_raises(Interrupt) { raiser.evaluate(new_invocation) } end def test_should_raise_subclass_of_interrupt_exception_with_default_message_so_it_works_in_ruby_1_8_6 exception_class = Class.new(Interrupt) raiser = ExceptionRaiser.new(exception_class, nil) - assert_raises(exception_class) { raiser.evaluate } + assert_raises(exception_class) { raiser.evaluate(new_invocation) } end end diff --git a/test/unit/return_values_test.rb b/test/unit/return_values_test.rb index 375427b32..c43aaca61 100644 --- a/test/unit/return_values_test.rb +++ b/test/unit/return_values_test.rb @@ -1,61 +1,66 @@ require File.expand_path('../../test_helper', __FILE__) +require 'mocha/invocation' require 'mocha/return_values' class ReturnValuesTest < Mocha::TestCase include Mocha + def new_invocation + Invocation.new(:irrelevant) + end + def test_should_return_nil values = ReturnValues.new - assert_nil values.next + assert_nil values.next(new_invocation) end def test_should_keep_returning_nil values = ReturnValues.new - values.next - assert_nil values.next - assert_nil values.next + values.next(new_invocation) + assert_nil values.next(new_invocation) + assert_nil values.next(new_invocation) end def test_should_return_evaluated_single_return_value values = ReturnValues.new(SingleReturnValue.new('value')) - assert_equal 'value', values.next + assert_equal 'value', values.next(new_invocation) end def test_should_keep_returning_evaluated_single_return_value values = ReturnValues.new(SingleReturnValue.new('value')) - values.next - assert_equal 'value', values.next - assert_equal 'value', values.next + values.next(new_invocation) + assert_equal 'value', values.next(new_invocation) + assert_equal 'value', values.next(new_invocation) end def test_should_return_consecutive_evaluated_single_return_values values = ReturnValues.new(SingleReturnValue.new('value_1'), SingleReturnValue.new('value_2')) - assert_equal 'value_1', values.next - assert_equal 'value_2', values.next + assert_equal 'value_1', values.next(new_invocation) + assert_equal 'value_2', values.next(new_invocation) end def test_should_keep_returning_last_of_consecutive_evaluated_single_return_values values = ReturnValues.new(SingleReturnValue.new('value_1'), SingleReturnValue.new('value_2')) - values.next - values.next - assert_equal 'value_2', values.next - assert_equal 'value_2', values.next + values.next(new_invocation) + values.next(new_invocation) + assert_equal 'value_2', values.next(new_invocation) + assert_equal 'value_2', values.next(new_invocation) end def test_should_build_single_return_values_for_each_values values = ReturnValues.build('value_1', 'value_2', 'value_3').values - assert_equal 'value_1', values[0].evaluate - assert_equal 'value_2', values[1].evaluate - assert_equal 'value_3', values[2].evaluate + assert_equal 'value_1', values[0].evaluate(new_invocation) + assert_equal 'value_2', values[1].evaluate(new_invocation) + assert_equal 'value_3', values[2].evaluate(new_invocation) end def test_should_combine_two_sets_of_return_values values1 = ReturnValues.build('value_1') values2 = ReturnValues.build('value_2a', 'value_2b') values = (values1 + values2).values - assert_equal 'value_1', values[0].evaluate - assert_equal 'value_2a', values[1].evaluate - assert_equal 'value_2b', values[2].evaluate + assert_equal 'value_1', values[0].evaluate(new_invocation) + assert_equal 'value_2a', values[1].evaluate(new_invocation) + assert_equal 'value_2b', values[2].evaluate(new_invocation) end end diff --git a/test/unit/single_return_value_test.rb b/test/unit/single_return_value_test.rb index 6941f91bb..6e7153c19 100644 --- a/test/unit/single_return_value_test.rb +++ b/test/unit/single_return_value_test.rb @@ -1,12 +1,17 @@ require File.expand_path('../../test_helper', __FILE__) +require 'mocha/invocation' require 'mocha/single_return_value' class SingleReturnValueTest < Mocha::TestCase include Mocha + def new_invocation + Invocation.new(:irrelevant) + end + def test_should_return_value value = SingleReturnValue.new('value') - assert_equal 'value', value.evaluate + assert_equal 'value', value.evaluate(new_invocation) end end diff --git a/test/unit/thrower_test.rb b/test/unit/thrower_test.rb index 2a9572302..cf1b4786c 100644 --- a/test/unit/thrower_test.rb +++ b/test/unit/thrower_test.rb @@ -1,18 +1,23 @@ require File.expand_path('../../test_helper', __FILE__) +require 'mocha/invocation' require 'mocha/thrower' class ThrowerTest < Mocha::TestCase include Mocha + def new_invocation + Invocation.new(:irrelevant) + end + def test_should_throw_tag thrower = Thrower.new(:tag) - assert_throws(:tag) { thrower.evaluate } + assert_throws(:tag) { thrower.evaluate(new_invocation) } end def test_should_throw_tag_with_return_value thrower = Thrower.new(:tag, 'return-value') - return_value = catch(:tag) { thrower.evaluate } + return_value = catch(:tag) { thrower.evaluate(new_invocation) } assert_equal 'return-value', return_value end end From 4e32c0c6915a46b50849e774ebf03e9e7183d9a3 Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Tue, 5 Nov 2019 19:41:35 +0000 Subject: [PATCH 26/28] display thrown object --- lib/mocha/invocation.rb | 6 ++++++ lib/mocha/thrower.rb | 3 ++- lib/mocha/thrown_object.rb | 12 ++++++++++++ ...ng_invocations_alongside_expectations_test.rb | 16 ++++++++++------ 4 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 lib/mocha/thrown_object.rb diff --git a/lib/mocha/invocation.rb b/lib/mocha/invocation.rb index 2cd19ab57..84d86c006 100644 --- a/lib/mocha/invocation.rb +++ b/lib/mocha/invocation.rb @@ -1,6 +1,7 @@ require 'mocha/parameters_matcher' require 'mocha/raised_exception' require 'mocha/return_values' +require 'mocha/thrown_object' require 'mocha/yield_parameters' module Mocha @@ -34,6 +35,11 @@ def raised(exception) @result = RaisedException.new(exception) end + # @private + def threw(tag, value) + @result = ThrownObject.new(tag, value) + end + # @private def mocha_inspect desc = "\n - #{@method_name}#{@arguments.mocha_inspect} # => #{@result.mocha_inspect}" diff --git a/lib/mocha/thrower.rb b/lib/mocha/thrower.rb index 0198e0c51..f3e4bc710 100644 --- a/lib/mocha/thrower.rb +++ b/lib/mocha/thrower.rb @@ -5,7 +5,8 @@ def initialize(tag, object = nil) @object = object end - def evaluate(_invocation) + def evaluate(invocation) + invocation.threw(@tag, @object) throw @tag, @object end end diff --git a/lib/mocha/thrown_object.rb b/lib/mocha/thrown_object.rb new file mode 100644 index 000000000..254b3dedf --- /dev/null +++ b/lib/mocha/thrown_object.rb @@ -0,0 +1,12 @@ +module Mocha + class ThrownObject + def initialize(tag, value = nil) + @tag = tag + @value = value + end + + def mocha_inspect + "threw (#{@tag.mocha_inspect}, #{@value.mocha_inspect})" + end + end +end diff --git a/test/acceptance/display_matching_invocations_alongside_expectations_test.rb b/test/acceptance/display_matching_invocations_alongside_expectations_test.rb index fd0676a94..4075a997b 100644 --- a/test/acceptance/display_matching_invocations_alongside_expectations_test.rb +++ b/test/acceptance/display_matching_invocations_alongside_expectations_test.rb @@ -19,16 +19,18 @@ def test_should_display_results test_result = run_as_test do foo = mock('foo') foo.expects(:bar).with(1).returns('a') - foo.stubs(:bar).with(any_parameters).returns('f').raises(StandardError) + foo.stubs(:bar).with(any_parameters).returns('f').raises(StandardError).throws(:tag, 'value') foo.bar(1, 2) { |_ignored| } assert_raise(StandardError) { foo.bar(3, 4) { |_ignored| } } + assert_throws(:tag) { foo.bar(5, 6) { |_ignored| } } end assert_invocations( test_result, - '- allowed any number of times, invoked twice: #.bar(any_parameters)', + '- allowed any number of times, invoked 3 times: #.bar(any_parameters)', ' - #.bar(1, 2) # => "f"', - ' - #.bar(3, 4) # => raised StandardError' + ' - #.bar(3, 4) # => raised StandardError', + ' - #.bar(5, 6) # => threw (:tag, "value")' ) end @@ -36,16 +38,18 @@ def test_should_display_yields test_result = run_as_test do foo = mock('foo') foo.expects(:bar).with(1).returns('a') - foo.stubs(:bar).with(any_parameters).multiple_yields(%w[b c], %w[d e]).returns('f').raises(StandardError) + foo.stubs(:bar).with(any_parameters).multiple_yields(%w[b c], %w[d e]).returns('f').raises(StandardError).throws(:tag, 'value') foo.bar(1, 2) { |_ignored| } assert_raise(StandardError) { foo.bar(3, 4) { |_ignored| } } + assert_throws(:tag) { foo.bar(5, 6) { |_ignored| } } end assert_invocations( test_result, - '- allowed any number of times, invoked twice: #.bar(any_parameters)', + '- allowed any number of times, invoked 3 times: #.bar(any_parameters)', ' - #.bar(1, 2) # => "f" after yielding ("b", "c"), then ("d", "e")', - ' - #.bar(3, 4) # => raised StandardError after yielding ("b", "c"), then ("d", "e")' + ' - #.bar(3, 4) # => raised StandardError after yielding ("b", "c"), then ("d", "e")', + ' - #.bar(5, 6) # => threw (:tag, "value") after yielding ("b", "c"), then ("d", "e")' ) end From 6284acb79b1c6cb52787c081f3008c2240f131d6 Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Wed, 6 Nov 2019 21:58:31 +0000 Subject: [PATCH 27/28] don't assert existence of other lines of failure messages checking that invocations follow 'satisfied expectations:' ensures the invocations are displayed in the correct position within the other lines --- ...y_matching_invocations_alongside_expectations_test.rb | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/test/acceptance/display_matching_invocations_alongside_expectations_test.rb b/test/acceptance/display_matching_invocations_alongside_expectations_test.rb index 4075a997b..f6a18ccad 100644 --- a/test/acceptance/display_matching_invocations_alongside_expectations_test.rb +++ b/test/acceptance/display_matching_invocations_alongside_expectations_test.rb @@ -70,12 +70,7 @@ def test_should_display_empty_yield_and_return def assert_invocations(test_result, *invocations) assert_failed(test_result) - assert_equal [ - 'not all expectations were satisfied', - 'unsatisfied expectations:', - '- expected exactly once, invoked never: #.bar(1)', - 'satisfied expectations:', - *invocations - ], test_result.failure_message_lines + assert_equal invocations.unshift('satisfied expectations:'), + test_result.failure_message_lines[-invocations.size..-1] end end From c061de07b2191c8b571043afef7114da255b4403 Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Wed, 6 Nov 2019 22:00:12 +0000 Subject: [PATCH 28/28] no need for blocks when expectation doesn't yield --- ...play_matching_invocations_alongside_expectations_test.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/acceptance/display_matching_invocations_alongside_expectations_test.rb b/test/acceptance/display_matching_invocations_alongside_expectations_test.rb index f6a18ccad..9c3e4de8c 100644 --- a/test/acceptance/display_matching_invocations_alongside_expectations_test.rb +++ b/test/acceptance/display_matching_invocations_alongside_expectations_test.rb @@ -21,9 +21,9 @@ def test_should_display_results foo.expects(:bar).with(1).returns('a') foo.stubs(:bar).with(any_parameters).returns('f').raises(StandardError).throws(:tag, 'value') - foo.bar(1, 2) { |_ignored| } - assert_raise(StandardError) { foo.bar(3, 4) { |_ignored| } } - assert_throws(:tag) { foo.bar(5, 6) { |_ignored| } } + foo.bar(1, 2) + assert_raise(StandardError) { foo.bar(3, 4) } + assert_throws(:tag) { foo.bar(5, 6) } end assert_invocations( test_result,