From 53e19bb79cadb74e2a55c9453f4e929c61834c52 Mon Sep 17 00:00:00 2001 From: Valentin ALBERT Date: Wed, 12 Nov 2025 12:03:00 +0100 Subject: [PATCH 1/2] Enhances RSpec/MultipleExpectations message to suggest aggregate_failures --- CHANGELOG.md | 1 + .../cop/rspec/multiple_expectations.rb | 3 ++- .../cop/rspec/multiple_expectations_spec.rb | 20 +++++++++---------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2d48b8a2..bd7ba81ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Fix a false positive for `RSpec/ReceiveNever` cop when `allow(...).to receive(...).never`. ([@ydah]) - Fix detection of nameless doubles with methods in `RSpec/VerifiedDoubles`. ([@ushi-as]) - Improve an offense message for `RSpec/RepeatedExample` cop. ([@ydah]) +- Improve `RSpec/MultipleExpectations` message to suggest using `aggregate_failures`. ([@svgr-slth]) - Let `RSpec/SpecFilePathFormat` leverage ActiveSupport inflections when configured. ([@corsonknowles], [@bquorning]) ## 3.7.0 (2025-09-01) diff --git a/lib/rubocop/cop/rspec/multiple_expectations.rb b/lib/rubocop/cop/rspec/multiple_expectations.rb index bb1a32297..de170029c 100644 --- a/lib/rubocop/cop/rspec/multiple_expectations.rb +++ b/lib/rubocop/cop/rspec/multiple_expectations.rb @@ -67,7 +67,8 @@ module RSpec # end # class MultipleExpectations < Base - MSG = 'Example has too many expectations [%d/%d].' + MSG = 'Example has too many expectations [%d/%d]. ' \ + 'Consider using `aggregate_failures` if these expectations are logically related.' ANYTHING = ->(_node) { true } TRUE_NODE = lambda(&:true_type?) diff --git a/spec/rubocop/cop/rspec/multiple_expectations_spec.rb b/spec/rubocop/cop/rspec/multiple_expectations_spec.rb index 32e8a43a0..10a495855 100644 --- a/spec/rubocop/cop/rspec/multiple_expectations_spec.rb +++ b/spec/rubocop/cop/rspec/multiple_expectations_spec.rb @@ -8,7 +8,7 @@ expect_offense(<<~RUBY) describe Foo do it 'uses expect twice' do - ^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [2/1]. + ^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [2/1]. Consider using `aggregate_failures` if these expectations are logically related. expect(foo).to eq(bar) expect(baz).to eq(bar) end @@ -34,7 +34,7 @@ expect_offense(<<~RUBY) describe Foo do it 'uses expect_any_instance_of twice' do - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [2/1]. + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [2/1]. Consider using `aggregate_failures` if these expectations are logically related. expect_any_instance_of(Foo).to receive(:bar) expect_any_instance_of(Foo).to receive(:baz) end @@ -46,7 +46,7 @@ expect_offense(<<~RUBY) describe Foo do it 'uses expect_any_instance_of twice' do - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [2/1]. + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [2/1]. Consider using `aggregate_failures` if these expectations are logically related. is_expected.to receive(:bar) is_expected.to receive(:baz) end @@ -58,7 +58,7 @@ expect_offense(<<~RUBY) describe Foo do it 'uses expect with block twice' do - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [2/1]. + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [2/1]. Consider using `aggregate_failures` if these expectations are logically related. expect { something }.to change(Foo.count) expect { something }.to change(Bar.count) end @@ -83,7 +83,7 @@ expect_offense(<<~RUBY) describe Foo do it 'has multiple aggregate_failures calls' do - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [2/1]. + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [2/1]. Consider using `aggregate_failures` if these expectations are logically related. aggregate_failures do end aggregate_failures do @@ -143,7 +143,7 @@ expect_offense(<<~RUBY) describe Foo, aggregate_failures: true do it 'uses expect twice', aggregate_failures: false do - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [2/1]. + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [2/1]. Consider using `aggregate_failures` if these expectations are logically related. expect(foo).to eq(bar) expect(baz).to eq(bar) end @@ -155,7 +155,7 @@ expect_offense(<<~RUBY) describe Foo do it 'uses expect twice', aggregate_failures: false do - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [2/1]. + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [2/1]. Consider using `aggregate_failures` if these expectations are logically related. expect(foo).to eq(bar) expect(baz).to eq(bar) end @@ -167,7 +167,7 @@ expect_offense(<<~RUBY) describe Foo, aggregate_failures: false do it 'uses expect twice' do - ^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [2/1]. + ^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [2/1]. Consider using `aggregate_failures` if these expectations are logically related. expect(foo).to eq(bar) expect(baz).to eq(bar) end @@ -179,7 +179,7 @@ expect_offense(<<~RUBY) describe Foo do it 'uses expect twice' do - ^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [2/1]. + ^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [2/1]. Consider using `aggregate_failures` if these expectations are logically related. expect(foo).to eq(bar) expect(baz).to eq(bar) end @@ -228,7 +228,7 @@ expect_offense(<<~RUBY) describe Foo do it 'uses expect three times' do - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [3/2]. + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [3/2]. Consider using `aggregate_failures` if these expectations are logically related. expect(foo).to eq(bar) expect(baz).to eq(bar) expect(qux).to eq(bar) From 7108f79b3024b0591432c6e0ac69f339add10647 Mon Sep 17 00:00:00 2001 From: Valentin ALBERT Date: Wed, 12 Nov 2025 12:27:14 +0100 Subject: [PATCH 2/2] Adapts message conditionnaly based on the aggregate failure flag --- CHANGELOG.md | 2 +- .../cop/rspec/multiple_expectations.rb | 21 ++++++++++++++----- .../cop/rspec/multiple_expectations_spec.rb | 6 +++--- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd7ba81ab..37cec1b01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ - Fix a false positive for `RSpec/ReceiveNever` cop when `allow(...).to receive(...).never`. ([@ydah]) - Fix detection of nameless doubles with methods in `RSpec/VerifiedDoubles`. ([@ushi-as]) - Improve an offense message for `RSpec/RepeatedExample` cop. ([@ydah]) -- Improve `RSpec/MultipleExpectations` message to suggest using `aggregate_failures`. ([@svgr-slth]) +- Improve `RSpec/MultipleExpectations` message to contextually suggest using `aggregate_failures` when appropriate. ([@svgr-slth]) - Let `RSpec/SpecFilePathFormat` leverage ActiveSupport inflections when configured. ([@corsonknowles], [@bquorning]) ## 3.7.0 (2025-09-01) diff --git a/lib/rubocop/cop/rspec/multiple_expectations.rb b/lib/rubocop/cop/rspec/multiple_expectations.rb index de170029c..929bf536a 100644 --- a/lib/rubocop/cop/rspec/multiple_expectations.rb +++ b/lib/rubocop/cop/rspec/multiple_expectations.rb @@ -67,11 +67,13 @@ module RSpec # end # class MultipleExpectations < Base - MSG = 'Example has too many expectations [%d/%d]. ' \ - 'Consider using `aggregate_failures` if these expectations are logically related.' + MSG = 'Example has too many expectations [%d/%d].' + MSG_SUGGEST_AGGREGATE = 'Example has too many expectations [%d/%d]. ' \ + 'Consider using `aggregate_failures` if these expectations are logically related.' ANYTHING = ->(_node) { true } TRUE_NODE = lambda(&:true_type?) + FALSE_NODE = lambda(&:false_type?) exclude_limit 'Max' @@ -102,7 +104,8 @@ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler self.max = expectations_count - flag_example(node, expectation_count: expectations_count) + flag_example(node, expectation_count: expectations_count, + aggregate_failures_disabled: aggregate_failures_disabled?(node)) end private @@ -114,6 +117,13 @@ def example_with_aggregate_failures?(example_node) aggregate_failures?(node_with_aggregate_failures, TRUE_NODE) end + def aggregate_failures_disabled?(example_node) + node_with_aggregate_failures = find_aggregate_failures(example_node) + return false unless node_with_aggregate_failures + + aggregate_failures?(node_with_aggregate_failures, FALSE_NODE) + end + def find_aggregate_failures(example_node) example_node.send_node.each_ancestor(:block) .find { |block_node| aggregate_failures?(block_node, ANYTHING) } @@ -130,11 +140,12 @@ def find_expectation(node, &block) end end - def flag_example(node, expectation_count:) + def flag_example(node, expectation_count:, aggregate_failures_disabled:) + message_template = aggregate_failures_disabled ? MSG : MSG_SUGGEST_AGGREGATE add_offense( node.send_node, message: format( - MSG, + message_template, total: expectation_count, max: max_expectations ) diff --git a/spec/rubocop/cop/rspec/multiple_expectations_spec.rb b/spec/rubocop/cop/rspec/multiple_expectations_spec.rb index 10a495855..66398b243 100644 --- a/spec/rubocop/cop/rspec/multiple_expectations_spec.rb +++ b/spec/rubocop/cop/rspec/multiple_expectations_spec.rb @@ -143,7 +143,7 @@ expect_offense(<<~RUBY) describe Foo, aggregate_failures: true do it 'uses expect twice', aggregate_failures: false do - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [2/1]. Consider using `aggregate_failures` if these expectations are logically related. + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [2/1]. expect(foo).to eq(bar) expect(baz).to eq(bar) end @@ -155,7 +155,7 @@ expect_offense(<<~RUBY) describe Foo do it 'uses expect twice', aggregate_failures: false do - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [2/1]. Consider using `aggregate_failures` if these expectations are logically related. + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [2/1]. expect(foo).to eq(bar) expect(baz).to eq(bar) end @@ -167,7 +167,7 @@ expect_offense(<<~RUBY) describe Foo, aggregate_failures: false do it 'uses expect twice' do - ^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [2/1]. Consider using `aggregate_failures` if these expectations are logically related. + ^^^^^^^^^^^^^^^^^^^^^^ Example has too many expectations [2/1]. expect(foo).to eq(bar) expect(baz).to eq(bar) end