Class: RuboCop::Cop::Kaizo::SpecSubject

Inherits:
Base
  • Object
show all
Includes:
AllowedMethods, AllowedPattern
Defined in:
lib/rubocop/cop/kaizo/spec_subject.rb

Overview

Requires the unit under test to be declared with subject, not let. subject is RSpec's name for the object being specified; hiding it in a let obscures which object the examples are about and forfeits is_expected/one-liner syntax.

A let (or let!) is flagged when the value it returns is confidently an instance of the class under test: the block's final expression is a .new call on described_class, on the constant named by an enclosing describe/context (full or short name), or on a constant matching the spec's file name (pool_spec.rb names Pool, api_client_spec.rb names APIClient).

A let that builds a second instance on purpose -- an other for an equality spec, say -- is exempted through AllowedMethods or AllowedPatterns, both matched against the let name.

There is no autocorrection: renaming the helper every example refers to is a change the spec's author should make deliberately.

Configuration

[+AllowedMethods+] let names never flagged. Default: none. [+AllowedPatterns+] Regexps matched against the let name; a match is exempt. Default: none. [+Include+] Files the cop runs on. Default: **/*_spec.rb.

Kaizo/SpecSubject:
AllowedMethods:
  - other       # a second instance for equality specs
AllowedPatterns:
  - '\Aother_'

Examples:

# bad
RSpec.describe Session::Pool do
  let(:pool) { described_class.new }
end

# good
RSpec.describe Session::Pool do
  subject(:pool) { described_class.new }
end

AllowedMethods: ['other'] (default: [])

# good - a deliberate second instance
RSpec.describe Session::Pool do
  subject(:pool) { described_class.new }

  let(:other) { described_class.new }
end

Constant Summary collapse

MSG =
"Declare the unit under test with `subject(:%<name>s)`, not `let`.".freeze

Instance Method Summary collapse

Instance Method Details

#constructed_class(node) ⇒ Object



67
68
69
# File 'lib/rubocop/cop/kaizo/spec_subject.rb', line 67

def_node_matcher :constructed_class, <<~PATTERN
  (send ${(send nil? :described_class) (const _ _)} :new ...)
PATTERN

#described_constant(node) ⇒ Object



72
73
74
# File 'lib/rubocop/cop/kaizo/spec_subject.rb', line 72

def_node_matcher :described_constant, <<~PATTERN
  (block (send {(const {nil? cbase} :RSpec) nil?} {:describe :context} $(const ...) ...) ...)
PATTERN

#let_declaration(node) ⇒ Object



62
63
64
# File 'lib/rubocop/cop/kaizo/spec_subject.rb', line 62

def_node_matcher :let_declaration, <<~PATTERN
  (block (send nil? {:let :let!} (sym $_)) _ $_)
PATTERN

#on_block(node) ⇒ Object



76
77
78
79
80
81
82
83
# File 'lib/rubocop/cop/kaizo/spec_subject.rb', line 76

def on_block(node)
  name, body = let_declaration(node)
  return unless name
  return if allowed_method?(name) || matches_allowed_pattern?(name.to_s)
  return unless unit_under_test?(node, final_expression(body))

  add_offense(node.send_node.loc.selector, message: format(MSG, name:))
end