diff --git a/changelog/fix_false_positives_for_minitest_no_assertion_test.md b/changelog/fix_false_positives_for_minitest_no_assertion_test.md new file mode 100644 index 00000000..2e78c052 --- /dev/null +++ b/changelog/fix_false_positives_for_minitest_no_assertion_test.md @@ -0,0 +1 @@ +* [#332](https://github.com/rubocop/rubocop-minitest/issues/332): Fix false positives for `Minitest/NoAssertions` when using matcher methods. ([@koic][]) diff --git a/lib/rubocop/cop/minitest/global_expectations.rb b/lib/rubocop/cop/minitest/global_expectations.rb index 3937a3be..8d22a698 100644 --- a/lib/rubocop/cop/minitest/global_expectations.rb +++ b/lib/rubocop/cop/minitest/global_expectations.rb @@ -87,20 +87,10 @@ class GlobalExpectations < Base MSG = 'Use `%s` instead.' - VALUE_MATCHERS = %i[ - must_be_empty must_equal must_be_close_to must_be_within_delta - must_be_within_epsilon must_include must_be_instance_of must_be_kind_of - must_match must_be_nil must_be must_respond_to must_be_same_as - path_must_exist path_wont_exist wont_be_empty wont_equal wont_be_close_to - wont_be_within_delta wont_be_within_epsilon wont_include wont_be_instance_of - wont_be_kind_of wont_match wont_be_nil wont_be wont_respond_to wont_be_same_as - ].freeze - - BLOCK_MATCHERS = %i[ - must_output must_pattern_match must_raise must_be_silent must_throw wont_pattern_match - ].freeze - - RESTRICT_ON_SEND = VALUE_MATCHERS + BLOCK_MATCHERS + VALUE_MATCHERS = MinitestExplorationHelpers::VALUE_MATCHERS + BLOCK_MATCHERS = MinitestExplorationHelpers::BLOCK_MATCHERS + + RESTRICT_ON_SEND = MinitestExplorationHelpers::MATCHER_METHODS # There are aliases for the `_` method - `expect` and `value` DSL_METHODS = %i[_ expect value].freeze diff --git a/lib/rubocop/cop/minitest/no_assertions.rb b/lib/rubocop/cop/minitest/no_assertions.rb index 4538aaa2..df1c6fd7 100644 --- a/lib/rubocop/cop/minitest/no_assertions.rb +++ b/lib/rubocop/cop/minitest/no_assertions.rb @@ -5,6 +5,8 @@ module Cop module Minitest # Checks if test cases contain any assertion calls. # + # Matchers such as `must_equal` and `wont_match` are also treated as assertion methods. + # # @example # # bad # class FooTest < Minitest::Test @@ -19,6 +21,23 @@ module Minitest # end # end # + # # bad + # class FooTest < ActiveSupport::TestCase + # describe 'foo' do + # it 'test equal' do + # end + # end + # end + # + # # good + # class FooTest < ActiveSupport::TestCase + # describe 'foo' do + # it 'test equal' do + # musts.must_equal expected_musts + # end + # end + # end + # class NoAssertions < Base include MinitestExplorationHelpers diff --git a/lib/rubocop/cop/mixin/minitest_exploration_helpers.rb b/lib/rubocop/cop/mixin/minitest_exploration_helpers.rb index 6e5bdaa6..01202214 100644 --- a/lib/rubocop/cop/mixin/minitest_exploration_helpers.rb +++ b/lib/rubocop/cop/mixin/minitest_exploration_helpers.rb @@ -10,7 +10,22 @@ module MinitestExplorationHelpers include DefNode extend NodePattern::Macros - ASSERTION_PREFIXES = %w[assert refute].freeze + VALUE_MATCHERS = %i[ + must_be_empty must_equal must_be_close_to must_be_within_delta + must_be_within_epsilon must_include must_be_instance_of must_be_kind_of + must_match must_be_nil must_be must_respond_to must_be_same_as + path_must_exist path_wont_exist wont_be_empty wont_equal wont_be_close_to + wont_be_within_delta wont_be_within_epsilon wont_include wont_be_instance_of + wont_be_kind_of wont_match wont_be_nil wont_be wont_respond_to wont_be_same_as + ].freeze + + BLOCK_MATCHERS = %i[ + must_output must_pattern_match must_raise must_be_silent must_throw wont_pattern_match + ].freeze + + MATCHER_METHODS = VALUE_MATCHERS + BLOCK_MATCHERS + + ASSERTION_PREFIXES = %i[assert refute].freeze LIFECYCLE_HOOK_METHODS_IN_ORDER = %i[ before_setup @@ -99,17 +114,18 @@ def assertions_count(node) end end + # rubocop:disable Metrics/CyclomaticComplexity def assertion_method?(node) return false unless node return assertion_method?(node.expression) if node.assignment? && node.respond_to?(:expression) return false unless node.type?(:send, :any_block) - ASSERTION_PREFIXES.any? do |prefix| - method_name = node.method_name + method_name = node.method_name + assertion_method = ASSERTION_PREFIXES.any? { |prefix| method_name.start_with?(prefix.to_s) } - method_name.start_with?(prefix) || node.method?(:flunk) - end + assertion_method || node.method?(:flunk) || MATCHER_METHODS.include?(method_name) end + # rubocop:enable Metrics/CyclomaticComplexity def lifecycle_hook_method?(node) node.def_type? && LIFECYCLE_HOOK_METHODS.include?(node.method_name) diff --git a/test/rubocop/cop/minitest/no_assertions_test.rb b/test/rubocop/cop/minitest/no_assertions_test.rb index 9fc3a669..19419722 100644 --- a/test/rubocop/cop/minitest/no_assertions_test.rb +++ b/test/rubocop/cop/minitest/no_assertions_test.rb @@ -78,6 +78,30 @@ def test_the_truth RUBY end + def test_register_no_offense_if_test_has_must_be_empty_assertion + assert_no_offenses(<<~RUBY) + class FooTest < Minitest::Test + describe 'foo' do + it 'must have good data' do + _(invalid_recs).must_be_empty ->{ do_something } + end + end + end + RUBY + end + + def test_register_no_offense_if_test_has_must_include_assertion + assert_no_offenses(<<~RUBY) + class FooTest < Minitest::Test + describe 'foo' do + it 'must include [false, true]' do + value([false, true]).must_include value + end + end + end + RUBY + end + def test_register_no_offense_for_unrelated_methods assert_no_offenses(<<~RUBY) class FooTest < Minitest::Test