From 7f944998bb998a9ffd21e4f155451d169fe4134a Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Wed, 23 Oct 2019 20:17:00 +0100 Subject: [PATCH 01/10] 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 63a46becb..59c2c315a 100644 --- a/lib/mocha/expectation.rb +++ b/lib/mocha/expectation.rb @@ -517,6 +517,7 @@ def initialize(mock, expected_method_name, backtrace = nil) @return_values = ReturnValues.new @yield_parameters = YieldParameters.new @backtrace = backtrace || caller + @returned_values = [] end # @private @@ -571,7 +572,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 @@ -604,6 +607,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 @@ -611,5 +615,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..57cd4bb15 --- /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 + 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 a9f16a80ab6a1ed1f4453dceb005f121a053344b Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Wed, 23 Oct 2019 21:46:26 +0100 Subject: [PATCH 02/10] Display exceptions raised from 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 59c2c315a..773027233 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}. @@ -517,7 +518,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 @@ -572,9 +573,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 # rubocop:disable Lint/RescueException + @results << RaisedException.new(e) + raise + end + @results.last end # @private @@ -619,7 +624,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 57cd4bb15..b5a741e16 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 ' - #.bar(2) # => "c"' ], test_result.failure_message_lines end + + def test_should_display_raised_exceptions + 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 d50891344bdfd09da2f7617dbc7a2cb0b0df0469 Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Thu, 24 Oct 2019 11:57:15 +0100 Subject: [PATCH 03/10] 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 | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 10 deletions(-) create mode 100644 lib/mocha/invocation.rb diff --git a/lib/mocha/expectation.rb b/lib/mocha/expectation.rb index 773027233..41a31e43d 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}. @@ -518,7 +518,7 @@ def initialize(mock, expected_method_name, backtrace = nil) @return_values = ReturnValues.new @yield_parameters = YieldParameters.new @backtrace = backtrace || caller - @results = [] + @invocations = [] end # @private @@ -570,16 +570,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 # rubocop:disable Lint/RescueException - @results << RaisedException.new(e) - raise - end - @results.last + invocation.call end # @private @@ -624,7 +620,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..380c40610 --- /dev/null +++ b/lib/mocha/invocation.rb @@ -0,0 +1,20 @@ +require 'mocha/raised_exception' + +module Mocha + class Invocation + def initialize(return_values) + @return_values = return_values + end + + def call + @result = @return_values.next + rescue Exception => e # rubocop:disable Lint/RescueException + @result = RaisedException.new(e) + raise + end + + def mocha_inspect + @result.mocha_inspect + end + end +end From 2a79b6cd74193b031c36cf55da223f09ccdc41cc Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Thu, 24 Oct 2019 12:09:00 +0100 Subject: [PATCH 04/10] 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 41a31e43d..df3327d00 100644 --- a/lib/mocha/expectation.rb +++ b/lib/mocha/expectation.rb @@ -570,12 +570,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 380c40610..e4fe4423d 100644 --- a/lib/mocha/invocation.rb +++ b/lib/mocha/invocation.rb @@ -2,11 +2,15 @@ module Mocha class Invocation - def initialize(return_values) + def initialize(yield_parameters, return_values) + @yield_parameters = yield_parameters @return_values = return_values end def call + @yield_parameters.next_invocation.each do |yield_parameters| + yield(*yield_parameters) + end @result = @return_values.next rescue Exception => e # rubocop:disable Lint/RescueException @result = RaisedException.new(e) From 976705cb41d7201e07bc1c3ce01036fea405a1bb Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Thu, 24 Oct 2019 15:25:31 +0100 Subject: [PATCH 05/10] Display yields of matching invocations --- lib/mocha/invocation.rb | 7 +++- ...invocations_alongside_expectations_test.rb | 39 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/lib/mocha/invocation.rb b/lib/mocha/invocation.rb index e4fe4423d..d609f6d74 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 @@ -5,10 +6,12 @@ class Invocation def initialize(yield_parameters, return_values) @yield_parameters = yield_parameters @return_values = return_values + @yields = [] end def call @yield_parameters.next_invocation.each do |yield_parameters| + @yields << ParametersMatcher.new(yield_parameters) yield(*yield_parameters) end @result = @return_values.next @@ -18,7 +21,9 @@ def call end 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 b5a741e16..779488559 100644 --- a/test/acceptance/display_matching_invocations_alongside_expectations_test.rb +++ b/test/acceptance/display_matching_invocations_alongside_expectations_test.rb @@ -57,4 +57,43 @@ def test_should_display_raised_exceptions ' - #.bar(2) # => raised StandardError' ], test_result.failure_message_lines end + + 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(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 + + 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(any_parameters) # => nil after yielding ()' + ], test_result.failure_message_lines + end end From 7a5fef9008dccadf7614326f04a1365483e8a513 Mon Sep 17 00:00:00 2001 From: James Mead Date: Fri, 8 Nov 2019 15:51:30 +0000 Subject: [PATCH 06/10] Rename block arguments when calling invocation from Expectation#invoke --- lib/mocha/expectation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mocha/expectation.rb b/lib/mocha/expectation.rb index df3327d00..ae0e126f2 100644 --- a/lib/mocha/expectation.rb +++ b/lib/mocha/expectation.rb @@ -572,7 +572,7 @@ def invoke perform_side_effects invocation = Invocation.new(@yield_parameters, @return_values) @invocations << invocation - invocation.call { |*args| yield(*args) } + invocation.call { |*yield_args| yield(*yield_args) } end # @private From 64724c72a3876aac055ca5d2811a46d918229a0e Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Thu, 24 Oct 2019 15:54:20 +0100 Subject: [PATCH 07/10] 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 | 14 +++++++---- lib/mocha/invocation.rb | 8 ++++--- lib/mocha/mock.rb | 4 ++-- ...invocations_alongside_expectations_test.rb | 24 ++++++++++++++++++- 4 files changed, 39 insertions(+), 11 deletions(-) diff --git a/lib/mocha/expectation.rb b/lib/mocha/expectation.rb index ae0e126f2..8c79ca542 100644 --- a/lib/mocha/expectation.rb +++ b/lib/mocha/expectation.rb @@ -567,12 +567,12 @@ def satisfied? end # @private - def invoke + def invoke(*arguments) @invocation_count += 1 perform_side_effects - invocation = Invocation.new(@yield_parameters, @return_values) + invocation = Invocation.new(method_name, @yield_parameters, @return_values) @invocations << invocation - invocation.call { |*yield_args| yield(*yield_args) } + invocation.call(*arguments) { |*yield_args| yield(*yield_args) } end # @private @@ -611,13 +611,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 d609f6d74..03568a58b 100644 --- a/lib/mocha/invocation.rb +++ b/lib/mocha/invocation.rb @@ -3,13 +3,15 @@ module Mocha class Invocation - 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 - 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) @@ -21,7 +23,7 @@ def call end 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 e917cd65b..6fb0fbb52 100644 --- a/lib/mocha/mock.rb +++ b/lib/mocha/mock.rb @@ -315,11 +315,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 779488559..056656c4e 100644 --- a/test/acceptance/display_matching_invocations_alongside_expectations_test.rb +++ b/test/acceptance/display_matching_invocations_alongside_expectations_test.rb @@ -78,6 +78,28 @@ def test_should_display_yields ], test_result.failure_message_lines end + def test_should_display_arguments + 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 + def test_should_display_empty_yield_and_return test_result = run_as_test do foo = mock('foo') @@ -93,7 +115,7 @@ def test_should_display_empty_yield_and_return '- expected exactly once, not yet invoked: #.bar(1)', 'satisfied expectations:', '- allowed any number of times, invoked once: #.bar(any_parameters)', - ' - #.bar(any_parameters) # => nil after yielding ()' + ' - #.bar(1, 2) # => nil after yielding ()' ], test_result.failure_message_lines end end From e916363a11772a8c89cd70cdc2b6acd10eb7c3e4 Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Thu, 24 Oct 2019 17:33:22 +0100 Subject: [PATCH 08/10] 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 | 71 ++++--------------- 1 file changed, 14 insertions(+), 57 deletions(-) diff --git a/test/acceptance/display_matching_invocations_alongside_expectations_test.rb b/test/acceptance/display_matching_invocations_alongside_expectations_test.rb index 056656c4e..82402e687 100644 --- a/test/acceptance/display_matching_invocations_alongside_expectations_test.rb +++ b/test/acceptance/display_matching_invocations_alongside_expectations_test.rb @@ -15,46 +15,24 @@ def teardown teardown_acceptance_test end - def test_should_display_return_values + 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(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) + assert_raise(StandardError) { foo.bar(3, 4) } 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 - 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)', + '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(2)', - ' - #.bar(2) # => "b"', - ' - #.bar(2) # => "c"', - ' - #.bar(2) # => raised StandardError' + '- 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 @@ -62,30 +40,10 @@ 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(2).multiple_yields(%w[b c], %w[d e]).returns('f') + foo.stubs(:bar).with(any_parameters).multiple_yields(%w[b c], %w[d e]).returns('f').raises(StandardError) - 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 - - def test_should_display_arguments - 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) + 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 '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 From c11c367df2c66e814addf0f849c5fc3a9a35a622 Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Thu, 24 Oct 2019 17:44:20 +0100 Subject: [PATCH 09/10] 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 8c79ca542..e8653e7d2 100644 --- a/lib/mocha/expectation.rb +++ b/lib/mocha/expectation.rb @@ -514,7 +514,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 @@ -558,17 +557,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) @invocations << invocation @@ -578,12 +576,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 @@ -596,11 +594,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 6ef520222d06f5988a415bc3bc9b9c214524e622 Mon Sep 17 00:00:00 2001 From: Nitish Rathi Date: Thu, 24 Oct 2019 19:27:58 +0100 Subject: [PATCH 10/10] DRY up acceptance test for display matching invocations Extract the assert_invocations method. Checking that invocations follow 'satisfied expectations:' ensures the invocations are displayed in the correct position within the other lines. --- ...invocations_alongside_expectations_test.rb | 36 ++++++++----------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/test/acceptance/display_matching_invocations_alongside_expectations_test.rb b/test/acceptance/display_matching_invocations_alongside_expectations_test.rb index 82402e687..236da47ed 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) assert_raise(StandardError) { foo.bar(3, 4) } 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,16 @@ def test_should_display_empty_yield_and_return 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:', + assert_invocations( + test_result, '- allowed any number of times, invoked once: #.bar(any_parameters)', ' - #.bar(1, 2) # => nil after yielding ()' - ], test_result.failure_message_lines + ) + end + + def assert_invocations(test_result, *invocations) + assert_failed(test_result) + assert_equal invocations.unshift('satisfied expectations:'), + test_result.failure_message_lines[-invocations.size..-1] end end