Class: RuboCop::Cop::RSpec::AggregateExamples

Inherits:
RuboCop::Cop
  • Object
show all
Includes:
Its, LineRangeHelpers, MatchersWithSideEffects, MetadataHelpers, NodeMatchers
Defined in:
lib/test_prof/cops/rspec/aggregate_examples.rb,
lib/test_prof/cops/rspec/aggregate_failures.rb,
lib/test_prof/cops/rspec/aggregate_examples/its.rb,
lib/test_prof/cops/rspec/aggregate_examples/node_matchers.rb,
lib/test_prof/cops/rspec/aggregate_examples/metadata_helpers.rb,
lib/test_prof/cops/rspec/aggregate_examples/line_range_helpers.rb,
lib/test_prof/cops/rspec/aggregate_examples/matchers_with_side_effects.rb

Overview

Checks if example groups contain two or more aggregatable examples.

This cop is primarily for reducing the cost of repeated expensive context initialization.

Block expectation syntax is deliberately not supported due to:

  1. ‘subject { -> { … } }` syntax being hard to detect, e.g. the following looks like an example with non-block syntax, but it might be, depending on how the subject is defined:

    it { is_expected.to do_something }
    

    If the subject is defined in a ‘shared_context`, it’s impossible to detect that at all.

  2. Aggregation should use composition with an ‘.and`. Also, aggregation of the `not_to` expectations is barely possible when a matcher doesn’t provide a negated variant.

  3. Aggregation of block syntax with non-block syntax should be in a specific order.

RSpec [comes with an ‘aggregate_failures` helper](relishapp.com/rspec/rspec-expectations/docs/aggregating-failures) not to fail the example on first unmet expectation that might come handy with aggregated examples. It can be [used in metadata form](relishapp.com/rspec/rspec-core/docs/expectation-framework-integration/aggregating-failures#use-%60:aggregate-failures%60-metadata), or [enabled globally](relishapp.com/rspec/rspec-core/docs/expectation-framework-integration/aggregating-failures#enable-failure-aggregation-globally-using-%60define-derived-metadata%60).

To match the style being used in the spec suite, AggregateExamples can be configured to add ‘:aggregate_failures` metadata to the example or not. The option not to add metadata can be also used when it’s not desired to make expectations after previously failed ones, commonly known as fail-fast.

The terms “aggregate examples” and “aggregate failures” not to be confused. The former stands for putting several expectations to a single example. The latter means to run all the expectations in the example instead of aborting on the first one.

Examples:


# bad
describe do
  specify do
    expect(number).to be_positive
    expect(number).to be_odd
  end

  it { is_expected.to be_prime }
end

# good
describe do
  specify do
    expect(number).to be_positive
    expect(number).to be_odd
    is_expected.to be_prime
  end
end

# fair - subject has side effects
describe do
  specify do
    expect(multiply_by(2)).to be_multiple_of(2)
  end

  specify do
    expect(multiply_by(3)).to be_multiple_of(3)
  end
end

Globally enable ‘aggregate_failures`


# spec/spec_helper.rb
config. do ||
  unless .key?(:aggregate_failures)
    [:aggregate_failures] = true
  end
end

AddAggregateFailuresMetadata: true (default)


# Metadata set using a symbol
specify(:aggregate_failures) do
  expect(number).to be_positive
  expect(number).to be_odd
end

AddAggregateFailuresMetadata: false


specify do
  expect(number).to be_positive
  expect(number).to be_odd
end

See Also:

Direct Known Subclasses

AggregateFailures

Defined Under Namespace

Modules: Its, LineRangeHelpers, MatchersWithSideEffects, MetadataHelpers, NodeMatchers

Constant Summary collapse

MSG =
"Aggregate with the example at line %d."

Constants included from Language

Language::RSPEC

Constants included from MatchersWithSideEffects

MatchersWithSideEffects::MSG_FOR_EXPECTATIONS_WITH_SIDE_EFFECTS

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.registryObject



7
8
9
# File 'lib/test_prof/cops/rspec/aggregate_failures.rb', line 7

def self.registry
  RuboCop::Cop::Cop.registry
end

Instance Method Details

#autocorrect(example_node) ⇒ Object



135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/test_prof/cops/rspec/aggregate_examples.rb', line 135

def autocorrect(example_node)
  clusters = example_clusters_for_autocorrect(example_node)
  return if clusters.empty?

  lambda do |corrector|
    clusters.each do |, examples|
      range = range_for_replace(examples)
      replacement = aggregated_example(examples, )
      corrector.replace(range, replacement)
      examples[1..-1].map { |example| drop_example(corrector, example) }
    end
  end
end

#on_block(node) ⇒ Object



123
124
125
126
127
128
129
130
131
132
133
# File 'lib/test_prof/cops/rspec/aggregate_examples.rb', line 123

def on_block(node)
  example_group_with_several_examples(node) do |all_examples|
    example_clusters(all_examples).each do |_, examples|
      examples[1..-1].each do |example|
        add_offense(example,
          location: :expression,
          message: message_for(example, examples[0]))
      end
    end
  end
end