When testing for exceptions in JUnit, it is easy to forget the call to `fail()`:

```java
try {
  someOperationThatShouldThrow();
  // forget to call Assert.fail()
} catch (SomeException expected) {
  assertThat(expected).hasMessage("Operation failed");
}
```

This is better:

```java
import static org.junit.Assert.fail;

try {
  someOperationThatShouldThrow();
  fail();
} catch (SomeException expected) {
  assertThat(expected).hasMessage("Operation failed");
}
```

But using `assertThrows` is preferable and the least error prone:

```java
import static org.junit.Assert.assertThrows;

SomeException expected =
    assertThrows(SomeException.class, () -> someOperationThatShouldThrow());
assertThat(expected).hasMessage("Operation failed");
```

Without the call to `fail()`, the test is broken: it will pass if the exception
is never thrown *or* if the exception is thrown with the expected message.

If the try/catch block is defensive and the exception may not always be thrown,
then the exception should be named 'tolerated'.

## Detection

This checker uses heuristics that identify as many occurrences of the problem as
possible while keeping the false positive rate low (low single-digit
percentages).

## Heuristics

Five methods were explored to detect missing `fail()` calls, triggering if no
`fail()` is used in a `try/catch` statement within a JUnit test class:

*   Cases in which the caught exception is called "expected".
*   Cases in which there is a call to an `assert*()` method in the catch block.
*   Cases in which "expected" shows up in a comment inside the `catch` block.
*   Cases in which the `catch` block is empty.
*   Cases in which the `try` block contains only a single statement.

Only the first three yield useful results and also required some more refinement
to reduce false positives. In addition, the checker does not trigger on comments
in the `catch` block due to implementation complexity.

To reduce false positives, no match is found if any of the following is true:

*   Any method with `fail` in its name is present in either catch or try block.
*   A `throw` statement or synonym (`assertTrue(false)`, etc.) is present in
    either `catch` or `try` block.
*   The occurrence happens inside a `setUp`, `tearDown`, `@Before`, `@After`,
    `suite` or`main` method.
*   The method returns from the `try` or `catch` block or immediately after.
*   The exception caught is of type `InterruptedException`, `AssertionError`,
    `junit.framework.AssertionFailedError` or `Throwable`.
*   The occurrence is inside a loop.
*   The try block contains a `while(true)` loop.
*   The `try` or `catch` block contains a `continue;` statement.
*   The `try/catch` statement also contains a `finally` statement.
*   A logging call is present in the `catch` block.

In addition, for occurrences which matched because they have a call to an
`assert*()` method in the catch block, no match is found if any of the following
characteristics are present:

*   A field assignment in the catch block.
*   A call to `assertTrue/False(boolean variable or field)` in the catch block.
*   The last statement in the `try` block is an `assert*()` (that is not a
    noop): `assertFalse(false)`, `assertTrue(true))` or `Mockito.verify()` call.
