View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2026 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.qulice.pmd.rules;
6   
7   import net.sourceforge.pmd.lang.java.ast.ASTBlock;
8   import net.sourceforge.pmd.lang.java.ast.ASTExpression;
9   import net.sourceforge.pmd.lang.java.ast.ASTMethodCall;
10  import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
11  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
12  import net.sourceforge.pmd.lang.java.rule.internal.TestFrameworksUtil;
13  import net.sourceforge.pmd.lang.java.symbols.JTypeDeclSymbol;
14  
15  /**
16   * Rule to check that JUnit/TestNG test methods contain at least one
17   * assertion. Unlike the PMD built-in
18   * {@code UnitTestShouldIncludeAssert} rule (PMD #4272), this
19   * implementation descends into lambda bodies, so an assertion placed
20   * inside a lambda passed to another method is still recognised.
21   *
22   * <p>It also treats the cactoos-matchers convention as an assertion: a
23   * no-argument {@code affirm()} call on an {@code Assertion} object, as in
24   * {@code new Assertion<>(...).affirm()}. PMD's own
25   * {@link TestFrameworksUtil#isProbableAssertCall(ASTMethodCall)} knows only
26   * about JUnit, TestNG, Hamcrest and AssertJ, so without this addition such
27   * tests are wrongly reported as missing an assertion (issue #1698).</p>
28   *
29   * @since 0.26.0
30   */
31  public final class UnitTestShouldIncludeAssertRule
32      extends AbstractJavaRulechainRule {
33  
34      /**
35       * Default constructor.
36       */
37      public UnitTestShouldIncludeAssertRule() {
38          super(ASTMethodDeclaration.class);
39      }
40  
41      @Override
42      public Object visit(final ASTMethodDeclaration method, final Object data) {
43          final ASTBlock body = method.getBody();
44          if (body != null
45              && TestFrameworksUtil.isTestMethod(method)
46              && !TestFrameworksUtil.isExpectAnnotated(method)
47              && body.descendants(ASTMethodCall.class)
48                  .crossFindBoundaries(true)
49                  .none(UnitTestShouldIncludeAssertRule::isAssertion)) {
50              asCtx(data).addViolation(method);
51          }
52          return data;
53      }
54  
55      private static boolean isAssertion(final ASTMethodCall call) {
56          return TestFrameworksUtil.isProbableAssertCall(call)
57              || UnitTestShouldIncludeAssertRule.isAffirmCall(call);
58      }
59  
60      private static boolean isAffirmCall(final ASTMethodCall call) {
61          final ASTExpression qualifier = call.getQualifier();
62          return "affirm".equals(call.getMethodName())
63              && call.getArguments().isEmpty()
64              && qualifier != null
65              && UnitTestShouldIncludeAssertRule.isAssertion(qualifier);
66      }
67  
68      private static boolean isAssertion(final ASTExpression expr) {
69          final JTypeDeclSymbol symbol = expr.getTypeMirror().getSymbol();
70          return symbol != null
71              && "Assertion".equals(symbol.getSimpleName());
72      }
73  }