diff --git a/config/default.yml b/config/default.yml index c66923ed..fed770bf 100644 --- a/config/default.yml +++ b/config/default.yml @@ -4,6 +4,9 @@ Minitest: Include: - '**/test/**/*' - '**/*_test.rb' + # Additional test base class names to recognize as test classes. + # Example: ['MyCustomBase', 'ApplicationTestCase'] + AdditionalTestBaseClasses: [] Minitest/AssertEmpty: Description: 'This cop enforces the test to use `assert_empty` instead of using `assert(object.empty?)`.' diff --git a/lib/rubocop/cop/mixin/minitest_exploration_helpers.rb b/lib/rubocop/cop/mixin/minitest_exploration_helpers.rb index 5116cc8f..fedc83db 100644 --- a/lib/rubocop/cop/mixin/minitest_exploration_helpers.rb +++ b/lib/rubocop/cop/mixin/minitest_exploration_helpers.rb @@ -6,7 +6,7 @@ module RuboCop module Cop # Helper methods for different explorations against test files and test cases. # @api private - module MinitestExplorationHelpers + module MinitestExplorationHelpers # rubocop:disable Metrics/ModuleLength include DefNode extend NodePattern::Macros @@ -40,8 +40,29 @@ module MinitestExplorationHelpers private + def additional_test_base_classes + return [] unless respond_to?(:cop_config) + + cop_config.fetch('AdditionalTestBaseClasses', []) + rescue StandardError + [] + end + + def test_base_classes + default_base_classes = %w[ + Minitest::Test + ActiveSupport::TestCase + ActionController::TestCase + ActionDispatch::IntegrationTest + ] + + default_base_classes + additional_test_base_classes + end + def test_class?(class_node) - class_node.parent_class && class_node.identifier.source.end_with?('Test') + return false unless class_node.parent_class + + test_base_classes.include?(class_node.parent_class.source) end def test_case?(node) diff --git a/test/rubocop/cop/minitest/duplicate_test_run_test.rb b/test/rubocop/cop/minitest/duplicate_test_run_test.rb index 03fee195..853ecc64 100644 --- a/test/rubocop/cop/minitest/duplicate_test_run_test.rb +++ b/test/rubocop/cop/minitest/duplicate_test_run_test.rb @@ -3,6 +3,10 @@ require_relative '../../../test_helper' class DuplicateTestRunTest < Minitest::Test + def config + RuboCop::Config.new('Minitest' => { 'AdditionalTestBaseClasses' => ['ParentTest'] }) + end + def test_registers_offense_when_parent_and_child_have_tests_methods assert_offense(<<~RUBY) class ParentTest < Minitest::Test @@ -89,7 +93,11 @@ class ClassTwo < ParentTest end def test_does_not_register_offense_if_the_class_is_not_a_test_class - assert_no_offenses(<<~RUBY) + # Use a cop without ParentTest in AdditionalTestBaseClasses + cop_config = RuboCop::Config.new('Minitest' => { 'AdditionalTestBaseClasses' => [] }) + cop_instance = RuboCop::Cop::Minitest::DuplicateTestRun.new(cop_config) + + offenses = inspect_source(<<~RUBY, cop_instance) class ParentTest < ExampleClass def test_child_asserts_twice assert_equal(1, 1) @@ -102,6 +110,8 @@ def test_child_asserts_twice end end RUBY + + assert_empty offenses end def test_does_not_throw_error_if_missing_parent_test_class diff --git a/test/rubocop/cop/minitest/no_test_cases_test.rb b/test/rubocop/cop/minitest/no_test_cases_test.rb index 48d75cc0..a33d329f 100644 --- a/test/rubocop/cop/minitest/no_test_cases_test.rb +++ b/test/rubocop/cop/minitest/no_test_cases_test.rb @@ -45,4 +45,33 @@ def perform; end end RUBY end + + def test_does_not_register_offense_for_non_test_parent_class + assert_no_offenses(<<~RUBY) + class FooTest < ApplicationRecord + def perform; end + end + RUBY + end + + def test_registers_offense_with_additional_test_base_classes_configuration + cop_config = RuboCop::Config.new('Minitest/NoTestCases' => { 'AdditionalTestBaseClasses' => ['MyCustomBase'] }) + cop_instance = RuboCop::Cop::Minitest::NoTestCases.new(cop_config) + + offenses = inspect_source(<<~RUBY, cop_instance) + class FooTest < MyCustomBase + end + RUBY + + assert_equal 1, offenses.size + assert_equal 'Test class should have test cases.', offenses.first.message + end + + def test_does_not_register_offense_without_additional_test_base_classes_configuration + assert_no_offenses(<<~RUBY) + class FooTest < MyCustomBase + def perform; end + end + RUBY + end end diff --git a/test/rubocop/cop/mixin/minitest_exploration_helpers_test.rb b/test/rubocop/cop/mixin/minitest_exploration_helpers_test.rb index 927610af..2c276a19 100644 --- a/test/rubocop/cop/mixin/minitest_exploration_helpers_test.rb +++ b/test/rubocop/cop/mixin/minitest_exploration_helpers_test.rb @@ -7,7 +7,7 @@ module Helper extend RuboCop::Cop::MinitestExplorationHelpers class << self - public :test_case? + public :test_case?, :test_class?, :test_base_classes end end @@ -42,6 +42,58 @@ def test_test_case_returns_false_for_test_methods_with_arguments refute Helper.test_case?(method_node(:test_with_arguments)) end + # Tests for test_class? method + def test_test_class_returns_true_for_minitest_test + source = 'class FooTest < Minitest::Test; end' + assert Helper.test_class?(parse_source!(source).ast) + end + + def test_test_class_returns_true_regardless_of_class_name + source = 'class Foo < Minitest::Test; end' + assert Helper.test_class?(parse_source!(source).ast) + end + + def test_test_class_returns_false_for_non_test_parent + source = 'class FooTest < ApplicationRecord; end' + refute Helper.test_class?(parse_source!(source).ast) + end + + def test_test_class_returns_true_for_activesupport_testcase + source = 'class FooTest < ActiveSupport::TestCase; end' + assert Helper.test_class?(parse_source!(source).ast) + end + + def test_test_class_returns_true_for_actioncontroller_testcase + source = 'class MyController < ActionController::TestCase; end' + assert Helper.test_class?(parse_source!(source).ast) + end + + def test_test_class_returns_true_for_actiondispatch_integrationtest + source = 'class MyIntegration < ActionDispatch::IntegrationTest; end' + assert Helper.test_class?(parse_source!(source).ast) + end + + def test_test_class_returns_false_without_parent_class + source = 'class FooTest; end' + refute Helper.test_class?(parse_source!(source).ast) + end + + def test_test_class_returns_false_for_unknown_parent + source = 'class TestHelper < ActionMailer::Base; end' + refute Helper.test_class?(parse_source!(source).ast) + end + + def test_test_base_classes_contains_default_classes + expected_classes = %w[ + Minitest::Test + ActiveSupport::TestCase + ActionController::TestCase + ActionDispatch::IntegrationTest + ] + + assert_equal expected_classes, Helper.test_base_classes + end + private def method_node(method_name)