Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 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)
Expand Down
18 changes: 15 additions & 3 deletions lib/rubocop/cop/rspec/multiple_expectations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,12 @@ module RSpec
#
class MultipleExpectations < Base
MSG = 'Example has too many expectations [%<total>d/%<max>d].'
MSG_SUGGEST_AGGREGATE = 'Example has too many expectations [%<total>d/%<max>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'

Expand Down Expand Up @@ -101,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
Expand All @@ -113,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) }
Expand All @@ -129,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
)
Expand Down
14 changes: 7 additions & 7 deletions spec/rubocop/cop/rspec/multiple_expectations_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down