Package groovy.junit6.plugin

# Annotation Interface ExpectedToFail

@Documented @Incubating @Retention(RUNTIME) @Target({TYPE,METHOD}) @ExtendWith(ExpectedToFailExtension.class) public @interface ExpectedToFail
Inverts the pass/fail outcome of the annotated test method (or every test method on the annotated class): a test that throws is reported as passing, and a test that completes normally is reported as failing.

Useful for asserting that some condition reliably fails, and for testing test-infrastructure code that should propagate failures.

Optionally constrain the expected failure by exception type or by a closure predicate evaluated against the thrown exception:

     @Test
     @ExpectedToFail(IllegalStateException)
     void mustThrowIse() { throw new IllegalStateException("boom") }

     @Test
     @ExpectedToFail({ ex instanceof RuntimeException && message.contains('boom') })
     void mustMatchPredicate() { throw new RuntimeException("kaboom!") }

Closure predicates are evaluated with three bindings: `ex` (the thrown exception), `message` (its message, possibly `null`), and `cause` (its cause, possibly `null`). The test is reported as passing iff the predicate returns a Groovy-truthy value.

For callers who want compile-time enforcement that the configured class is a `Throwable` subclass, set `exception()` instead of (or alongside) `value()`. `exception` and a closure `value` compose: the type acts as a guard that runs before the predicate, so the closure body can drop the `ex instanceof X` boilerplate. `exception` and a `Throwable` `value` are mutually exclusive (they would specify the type twice).

`TestAbortedException` (the exception thrown by JUnit `Assumptions`) is never treated as the expected failure; it's always rethrown so the test is reported as aborted.

Composition with `ForkedJvm`: works in either declaration order via explicit coordination between the two extensions (no reliance on annotation iteration order). When `@ExpectedToFail` is declared before `@ForkedJvm` (i.e., outer), the inversion happens in the parent JVM after the failure propagates from the fork — exercising `@ForkedJvm`'s serialization. When declared after (inner), the inversion happens inside the forked child.

Since:
  6.0.0

- ## Optional Element Summary

  Optional Elements
  Modifier and Type
  Optional Element
  Description
  `Class<? extends Throwable>`
  `exception`
  Type-safe alternative to `value()`: declares the expected exception type with compile-time enforcement that it is a `Throwable` subclass.
  `String`
  `reason`
  Optional human-readable explanation of why this test is expected to fail.
  `Class<?>`
  `value`
  Either an expected `Throwable` subclass or a `Closure` predicate evaluated against the thrown exception.

- ## Element Details

  - ### value

    Class<?> value
    Either an expected `Throwable` subclass or a `Closure` predicate evaluated against the thrown exception. Defaults to `Throwable`, which matches anything.

    When set to a `Throwable` subclass, mutually exclusive with `exception()`. When set to a closure, composes with `exception()`: the type guard is checked first, then the closure predicate.

    Default:
      java.lang.Throwable.class

  - ### exception

    Class<? extends Throwable> exception
    Type-safe alternative to `value()`: declares the expected exception type with compile-time enforcement that it is a `Throwable` subclass. Defaults to `Throwable`, which matches anything.

    Mutually exclusive with a `Throwable` `value()`; composes with a closure `value()` as a type guard evaluated before the predicate.

    Default:
      java.lang.Throwable.class

  - ### reason

    String reason
    Optional human-readable explanation of why this test is expected to fail. Surfaced in the failure message when the test unexpectedly passes.
    Default:
      ""