Class: RuboCop::Cop::RailsCTO::MinitestSubject

Inherits:
Base
  • Object
show all
Defined in:
lib/rubocop/cop/rails_cto/minitest_subject.rb

Overview

Enforces the mandatory Minitest::Spec ‘subject` convention from the rails-cto plugin’s rails-cto-minitest skill:

1. Every `*_test.rb` test class must define `subject { ... }` exactly
   once at the top level of the class body.
2. `subject` must not be reassigned inside a nested `describe` or
   `it` block.

Examples:

# bad — no subject at top level
class UserTest < ActiveSupport::TestCase
  describe "#full_name" do
    it "returns the name" do
      user = Fabricate(:user)
      assert_equal "Ada", user.full_name
    end
  end
end

# bad — subject reassigned inside nested describe
class UserTest < ActiveSupport::TestCase
  subject { Fabricate(:user) }

  describe "#admin?" do
    subject { Fabricate(:user, role: :admin) } # offense
  end
end

# good
class UserTest < ActiveSupport::TestCase
  let(:attributes) { {} }
  subject { Fabricate.build(:user, **attributes) }

  describe "#admin?" do
    let(:attributes) { { role: :admin } }

    it "returns true" do
      assert_predicate subject, :admin?
    end
  end
end

Constant Summary collapse

MSG_MISSING =
"Define `subject { ... }` exactly once at the top of the test class."
MSG_NESTED =
"Do not reassign `subject` inside nested describe/it blocks."

Instance Method Summary collapse

Instance Method Details

#on_class(node) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/rubocop/cop/rails_cto/minitest_subject.rb', line 59

def on_class(node)
  return unless test_file?

  body = node.body
  return unless body

  children = body.begin_type? ? body.children : [body]
  return unless contains_spec_constructs?(children)

  add_offense(node, message: MSG_MISSING) if children.none? { |child| subject_block?(child) }
  nested_subject_blocks(children).each { |nested| add_offense(nested, message: MSG_NESTED) }
end