View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2026 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.qulice.checkstyle;
6   
7   import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
8   import com.puppycrawl.tools.checkstyle.api.DetailAST;
9   import com.puppycrawl.tools.checkstyle.api.TokenTypes;
10  
11  /**
12   * Forbids the {@code expected} parameter on JUnit's {@code @Test} annotation.
13   *
14   * <p>The JUnit 4 construct {@code @Test(expected = SomeException.class)}
15   * is too coarse: the whole test body is inspected for the exception, so a
16   * failure during set-up (e.g. an unexpected {@code NullPointerException})
17   * silently satisfies the assertion. In addition, nothing can be checked
18   * about the message or the cause of the thrown exception. Prefer
19   * {@code Assertions.assertThrows(...)} (JUnit 5) or a Hamcrest-style
20   * {@code try}/{@code catch} with explicit assertions instead. See
21   * <a href="https://github.com/yegor256/qulice/issues/668">#668</a>.</p>
22   *
23   * @since 0.24
24   */
25  public final class ProhibitTestExpectedCheck extends AbstractCheck {
26  
27      /**
28       * Default constructor.
29       */
30      public ProhibitTestExpectedCheck() {
31          // nothing to initialize
32      }
33  
34      @Override
35      public int[] getDefaultTokens() {
36          return this.getRequiredTokens();
37      }
38  
39      @Override
40      public int[] getAcceptableTokens() {
41          return this.getRequiredTokens();
42      }
43  
44      @Override
45      public int[] getRequiredTokens() {
46          return new int[] {TokenTypes.ANNOTATION};
47      }
48  
49      @Override
50      public void visitToken(final DetailAST ast) {
51          if (ProhibitTestExpectedCheck.isTest(ast)
52              && ProhibitTestExpectedCheck.hasExpected(ast)) {
53              this.log(
54                  ast.getLineNo(),
55                  "@Test(expected = ...) is not allowed, use Assertions.assertThrows() instead"
56              );
57          }
58      }
59  
60      private static boolean isTest(final DetailAST ast) {
61          final DetailAST ident = ast.findFirstToken(TokenTypes.IDENT);
62          final boolean match;
63          if (ident == null) {
64              final DetailAST dot = ast.findFirstToken(TokenTypes.DOT);
65              match = dot != null
66                  && dot.getLastChild() != null
67                  && "Test".equals(dot.getLastChild().getText());
68          } else {
69              match = "Test".equals(ident.getText());
70          }
71          return match;
72      }
73  
74      private static boolean hasExpected(final DetailAST ast) {
75          boolean found = false;
76          DetailAST child = ast.getFirstChild();
77          while (child != null) {
78              if (child.getType() == TokenTypes.ANNOTATION_MEMBER_VALUE_PAIR) {
79                  final DetailAST name = child.findFirstToken(TokenTypes.IDENT);
80                  if (name != null && "expected".equals(name.getText())) {
81                      found = true;
82                      break;
83                  }
84              }
85              child = child.getNextSibling();
86          }
87          return found;
88      }
89  }