Class: RuboCop::Cop::RSpecGuide::DuplicateBeforeHooks

Inherits:
RuboCop::Cop::RSpec::Base
  • Object
show all
Defined in:
lib/rubocop/cop/rspec_guide/duplicate_before_hooks.rb

Overview

Detects duplicate before hooks with identical code across sibling contexts.

When the same before hook appears in multiple sibling contexts, it indicates one of two problems:

  1. ERROR: Duplicate in ALL contexts → must extract to parent
  2. WARNING: Duplicate in SOME contexts → suggests poor test hierarchy

Examples:

ERROR - duplicate in ALL contexts

# bad - before hook duplicated in all 2 contexts
describe 'Controller' do
  context 'as admin' do
    before { (user) }
    it { expect(response).to be_successful }
  end

  context 'as guest' do
    before { (user) }  # ERROR: in all contexts!
    it { expect(response).to be_forbidden }
  end
end

# good - extracted to parent
describe 'Controller' do
  before { (user) }  # Moved to parent

  context 'as admin' do
    it { expect(response).to be_successful }
  end

  context 'as guest' do
    it { expect(response).to be_forbidden }
  end
end

WARNING - duplicate in SOME contexts

# bad - before hook duplicated in 2/3 contexts (code smell)
describe 'API' do
  context 'scenario A' do
    before { setup_api }
    it { expect(response).to be_ok }
  end

  context 'scenario B' do
    before { setup_api }  # WARNING: duplicated in 2/3
    it { expect(response).to be_ok }
  end

  context 'scenario C' do
    before { setup_different_api }  # Different setup
    it { expect(response).to be_ok }
  end
end

# good - refactor hierarchy
describe 'API' do
  context 'with standard setup' do
    before { setup_api }

    context 'scenario A' do
      it { expect(response).to be_ok }
    end

    context 'scenario B' do
      it { expect(response).to be_ok }
    end
  end

  context 'with different setup' do
    before { setup_different_api }

    context 'scenario C' do
      it { expect(response).to be_ok }
    end
  end
end

Configuration

# To disable warnings for partial duplicates:
RSpecGuide/DuplicateBeforeHooks:
  WarnOnPartialDuplicates: false  # Only report full duplicates

Edge case - different hooks

# good - different before hooks (no duplicate)
describe 'Service' do
  context 'with user A' do
    before { (user_a) }
    it { expect(service.call).to be_success }
  end

  context 'with user B' do
    before { (user_b) }  # Different, OK
    it { expect(service.call).to be_success }
  end
end

Constant Summary collapse

MSG_ERROR =
"Duplicate `before` hook in ALL sibling contexts. " \
"Extract to parent context."
MSG_WARNING =
"Before hook duplicated in %<count>d/%<total>d sibling contexts. " \
"Consider refactoring test hierarchy - this suggests poor organization."

Instance Method Summary collapse

Instance Method Details

#before_hook_with_body?(node) ⇒ Object



120
121
122
123
124
125
# File 'lib/rubocop/cop/rspec_guide/duplicate_before_hooks.rb', line 120

def_node_matcher :before_hook_with_body?, <<~PATTERN
  (block
    (send nil? :before ...)
    (args)
    $_body)
PATTERN

#context_only?(node) ⇒ Object



115
116
117
# File 'lib/rubocop/cop/rspec_guide/duplicate_before_hooks.rb', line 115

def_node_matcher :context_only?, <<~PATTERN
  (block (send nil? :context ...) ...)
PATTERN

#on_block(node) ⇒ Object



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/rubocop/cop/rspec_guide/duplicate_before_hooks.rb', line 127

def on_block(node)
  # Fast pre-check: only process describe/context blocks
  return unless node.method?(:describe) || node.method?(:context)
  return unless example_group?(node)

  # Collect all sibling contexts
  sibling_contexts = collect_sibling_contexts(node)
  return if sibling_contexts.size < 2

  # Collect before hooks from each context
  befores_by_context = sibling_contexts.map do |ctx|
    collect_before_hooks(ctx)
  end

  # Find duplicates
  find_duplicate_befores(befores_by_context)
end