2

I defined my own JUnit annotation:

@ParameterizedTest
@MethodSource("myorg.qa.ccrtesting.DataProviders#standardDataProvider")
@Tags({@Tag("ccr"), @Tag("standard")})
public @interface CcrStandardTest {
}

Then, I was able to use that annotation in my tests:

@CcrStandardTest
public void E0010_contact_standard (String testData) {
...
  • My run configuration:
    JVM options: -ea
    Class: myorg.qa.ccrtesting.ccrstandardtests.CcrStanConTest - This was suggested by the IDE (and is verified to point to the correct class, which holds my prototype test method)

However, this results in: jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter [java.lang.String arg0] in method [public void...

  • I tried removing String testData from the test method signature but then JUnit is not executing any tests: No tests found

  • When I add @Test above my prototype test method, it executes but:

    1. It seems like none of the annotations I defined under @CcrStandardTest are applied
    2. IDE suggests suspicious combination @Test and parameterized source
      (I already know @ParameterizedTest implies @Test, just not sure why IDE is able to find the custom annotation but JUnit isn't?)
DraxDomax
  • 1,008
  • 1
  • 9
  • 28

2 Answers2

5

As you've discovered, you need to add @Retention(RUNTIME) to your composed annotation in order for JUnit to see it. Annotations in Java have three different retention policies:

  • RetentionPolicy.SOURCE

    Annotations are to be discarded by the compiler.

  • RetentionPolicy.CLASS

    Annotations are to be recorded in the class file by the compiler but need not be retained by the VM at run time. This is the default behavior. [emphasis added]

  • RetentionPolicy.RUNTIME

    Annotations are to be recorded in the class file by the compiler and retained by the VM at run time, so they may be read reflectively.

As I emphasized above, if you don't explicitly add @Retention(...) then the CLASS policy is used. This won't work with JUnit because JUnit doesn't scan the *.class files (i.e. byte-code) for the annotations, it scans the loaded classes reflectively to find test methods. Without a RUNTIME retention policy your annotation is not reflectively accessible, thus JUnit never sees it and consequently doesn't execute the test.

The @Target annotation:

Indicates the contexts in which an annotation type is applicable. The declaration contexts and type contexts in which an annotation type may be applicable are specified in JLS 9.6.4.1, and denoted in source code by enum constants of java.lang.annotation.ElementType.

If an @Target meta-annotation is not present on an annotation type T , then an annotation of type T may be written as a modifier for any declaration except a type parameter declaration.

If an @Target meta-annotation is present, the compiler will enforce the usage restrictions indicated by ElementType enum constants, in line with JLS 9.7.4.

In my answer to your other question I used:

@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})

Because that's the same targets used by @ParameterizedTest. I figured it was a good idea to restrict it to METHOD since the designers of @ParameterizedTest apparently feel that only methods should be directly extended by the parameterized-tests extension (see §5 Extension Model). And including ANNOTATION_TYPE allows you to place your composed annotation on another annotation, creating yet another composed annotation.

You'll also see I included @Documented:

If the annotation @Documented is present on the declaration of an annotation type A, then any @A annotation on an element is considered part of the element's public contract. In more detail, when an annotation type A is annotated with Documented, the presence and value of annotations of type A are a part of the public contract of the elements A annotates. Conversely, if an annotation type B is not annotated with Documented, the presence and value of B annotations are not part of the public contract of the elements B annotates. Concretely, if an annotation type is annotated with Documented, by default a tool like javadoc will display annotations of that type in its output while annotations of annotation types without Documented will not be displayed.

Notice that theses annotations—@Retention, @Target, and @Documented—are not specific to JUnit. These annotations are fundamental to how annotations in Java work and each one resides in the java.lang.annotation package.

Slaw
  • 37,820
  • 8
  • 53
  • 80
  • Beautiful, thanks again and many times over - thanks! I am ashamed I haven't reached Java Annotations in my studies yet. Blame it on the usual aggressive release-oriented team goals that leave no time to study theory... – DraxDomax Feb 21 '20 at 14:24
  • 1
    Glad to help :) Note there are three other annotations in [`java.lang.annotation`](https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/lang/annotation/package-summary.html): `@Inherited`, `@Native` and `@Repeatable`. You may want to read up on those as well. If you look at my answer to your other question you'll see I don't wrap the two `@Tag` annotations in an `@Tags({...})` annotation. This is because `@Tag` is annotated with `@Repeatable(Tags.class)`. Of course, you can continue using `@Tags` if you prefer. – Slaw Feb 21 '20 at 14:28
0

Upon some investigation, I found that by adding: @Retention(RUNIME) (two imports will be required), meta annotation definition will be resolved.

JUnit docs actually show this (@Retention) in their example of composed annotations.
They also show @Target being used with it as well.
- But explain neither...

This answer isn't the highest quality, as I don't know what @Retention and @Target do but I hope it'll help anyone stuck with the same problem as me to get going.

If someone elaborates, I will be happy to edit this answer or accept theirs!

DraxDomax
  • 1,008
  • 1
  • 9
  • 28