Skip to content

Commit

Permalink
[Fix rubocop#279] Add new Minitest/NonExecutableTestMethod cop
Browse files Browse the repository at this point in the history
Resolves rubocop#279.

This PR adds new `Minitest/NonExecutableTestMethod` cop, which checks for
the use of test methods outside of a test class.
Test methods should be defined within a test class to ensure their execution.

NOTE: This cop assumes that classes whose superclass name includes the word
"`Test`" are test classes, in order to prevent false positives.

```ruby
# bad
class FooTest < Minitest::Test
end
def test_method_should_be_inside_test_class
end

# good
class FooTest < Minitest::Test
  def test_method_should_be_inside_test_class
  end
end
```
  • Loading branch information
koic committed Dec 14, 2023
1 parent 01daadd commit 3688cc9
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 0 deletions.
6 changes: 6 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,12 @@ Minitest/NoTestCases:
Enabled: false
VersionAdded: '0.30'

Minitest/NonExecutableTestMethod:
Description: 'Checks uses of test methods outside test class.'
Enabled: pending
Severity: warning
VersionAdded: '<<next>>'

Minitest/NonPublicTestMethod:
Description: 'Detects non `public` (marked as `private` or `protected`) test methods.'
Enabled: pending
Expand Down
51 changes: 51 additions & 0 deletions lib/rubocop/cop/minitest/non_executable_test_method.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Minitest
# Checks for the use of test methods outside of a test class.
#
# Test methods should be defined within a test class to ensure their execution.
#
# NOTE: This cop assumes that classes whose superclass name includes the word
# "`Test`" are test classes, in order to prevent false positives.
#
# @example
#
# # bad
# class FooTest < Minitest::Test
# end
# def test_method_should_be_inside_test_class
# end
#
# # good
# class FooTest < Minitest::Test
# def test_method_should_be_inside_test_class
# end
# end
#
class NonExecutableTestMethod < Base
include MinitestExplorationHelpers

MSG = 'Test method should be defined inside a test class to ensure execution.'

def on_def(node)
return if !test_method?(node) || !use_test_class?
return if node.left_siblings.none? { |sibling| possible_test_class?(sibling) }

add_offense(node)
end

def use_test_class?
root_node = processed_source.ast

root_node.each_descendant(:class).any? { |class_node| test_class?(class_node) }
end

def possible_test_class?(node)
node.is_a?(AST::ClassNode) && test_class?(node) && node.parent_class.source.include?('Test')
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/rubocop/cop/minitest_cops.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
require_relative 'minitest/assert_truthy'
require_relative 'minitest/duplicate_test_run'
require_relative 'minitest/empty_line_before_assertion_methods'
require_relative 'minitest/non_executable_test_method'
require_relative 'minitest/redundant_message_argument'
require_relative 'minitest/return_in_test_method'
require_relative 'minitest/test_file_name'
Expand Down
158 changes: 158 additions & 0 deletions test/rubocop/cop/minitest/non_executable_test_method_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# frozen_string_literal: true

require_relative '../../../test_helper'

class NonExecutableTestMethodTest < Minitest::Test
def test_registers_offense_when_test_method_is_defined_outside_minitest_test_class
assert_offense(<<~RUBY)
class FooTest < Minitest::Test
end
def test_foo
^^^^^^^^^^^^ Test method should be defined inside a test class to ensure execution.
end
RUBY
end

def test_registers_offense_when_test_method_is_defined_outside_active_support_test_case_class
assert_offense(<<~RUBY)
class FooTest < ActiveSupport::TestCase
end
def test_foo
^^^^^^^^^^^^ Test method should be defined inside a test class to ensure execution.
end
RUBY
end

def test_does_not_register_offense_when_test_method_is_defined_outside_namespaced_test_class
assert_offense(<<~RUBY)
module M
class FooTest < Minitest::Test
def test_foo
end
end
def test_bar
^^^^^^^^^^^^ Test method should be defined inside a test class to ensure execution.
end
end
RUBY
end

def test_does_not_register_offense_when_non_test_method_is_defined_outside_test_class
assert_no_offenses(<<~RUBY)
class FooTest < Minitest::Test
end
def do_something
end
RUBY
end

def test_does_not_register_offense_when_non_test_method_is_defined_outside_active_support_test_case_class
assert_no_offenses(<<~RUBY)
class FooTest < ActiveSupport::TestCase
end
def do_something
end
RUBY
end

def test_does_not_register_offense_when_test_method_is_defined_inside_test_class
assert_no_offenses(<<~RUBY)
class FooTest < Minitest::Test
def test_foo
end
end
RUBY
end

def test_does_not_register_offense_when_test_method_is_defined_inside_test_helper_class
assert_no_offenses(<<~RUBY)
class FooTest < Minitest::Test
def test_foo
end
end
class TestHelperMailer < ActionMailer::Base
def test_parameter_args
end
end
RUBY
end

def test_does_not_register_offense_when_test_method_is_defined_inside_test_helper_module
assert_no_offenses(<<~RUBY)
class FooTest < Minitest::Test
def test_foo
end
end
module TestHelper
def test_parameter_args
end
end
RUBY
end

def test_does_not_register_offense_when_test_class_in_which_test_method_is_defined_is_repeated
assert_no_offenses(<<~RUBY)
class FooTest < Minitest::Test
def test_foo
end
end
class FooTest < Minitest::Test
def test_foo
end
end
RUBY
end

def test_does_not_register_offense_when_test_method_is_defined_and_test_class_is_not_defined
assert_no_offenses(<<~RUBY)
def test_something
end
RUBY
end

def test_does_not_register_offense_when_test_method_is_defined_inside_condition
assert_no_offenses(<<~RUBY)
module ActiveRecord
class AdapterTest < ActiveRecord::TestCase
unless current_adapter?(:PostgreSQLAdapter)
def test_update_prepared_statement
end
end
end
end
RUBY
end

def test_does_not_register_offense_when_nested_test_method_and_two_test_classes_are_defined
assert_no_offenses(<<~RUBY)
class FooTest < ActiveRecord::TestCase
def test_foo
def test_bar
end
end
end
class BarTest < ActiveRecord::TestCase
end
RUBY
end

def test_does_not_register_offense_when_non_test_case_class_is_defined_before_test_method
assert_no_offenses(<<~RUBY)
class SetTest < ActiveRecord::AbstractMysqlTestCase
class SetTest < ActiveRecord::Base
end
def test_should_not_be_unsigned
column = SetTest.columns_hash["set_column"]
assert_not_predicate column, :unsigned?
end
end
RUBY
end
end

0 comments on commit 3688cc9

Please sign in to comment.