javajunitjunit4junit5test-runner

How to access an annotation value (JUnit 4 -> JUnit 5)


I am trying to port a project from JUnit 4 to JUnit 5. The project includes a custom runner that has a listener that detects whether a test has a certain annotation (@GradedTest) and accesses the annotation's key-value pairs. For example, it would be able to access the values associated with name and points in this code:

@Test
@GradedTest(name = "greet() test", points = "1")
public void defaultGreeting() {
    assertEquals(GREETING, unit.greet());
}

The existing JUnit 4 code has a listener that extends RunListener and overrides testStarted():

@Override
public void testStarted(Description description) throws Exception {
    super.testStarted(description);

    this.currentGradedTestResult = null;

    GradedTest gradedTestAnnotation = description.getAnnotation(GradedTest.class);
    if (gradedTestAnnotation != null) {
        this.currentGradedTestResult =  new GradedTestResult(
                gradedTestAnnotation.name(),
                gradedTestAnnotation.number(),
                gradedTestAnnotation.points(),
                gradedTestAnnotation.visibility()
        );
    }
}

Note that this makes use of Description.getAnnotation().

I am trying to switch to the JUnit Platform Launcher API. I can use a LauncherDiscoveryRequestBuilder to select the tests I want to run, and I can create listeners that extend SummaryGeneratingListener and override executionStarted(TestIdentifier testIdentifier). I see no way, however, to get an annotation and its values from a TestIdentifier.

What is the JUnit 5 equivalent of Description.getAnnotation() or the new way of getting a test annotation's values?


Solution

  • I did find a way to get annotations, but I do not know how robust it is. This is how I overrode SummaryGeneratingListener.executionStarted(TestIdentifier identifier):

    @Override
    public void executionStarted(TestIdentifier identifier) {
        super.executionStarted(identifier);
        this.currentGradedTestResult = null;
    
        // Check if this is an atomic test, not a container.
        if (identifier.isTest()) {
            // Check if the test's source is provided.
            TestSource source = identifier.getSource().orElse(null);
            // If so, and if it's a MethodSource, get and use the annotation if present.
            if (source != null && source instanceof MethodSource) {
                GradedTest gradedTestAnnotation = ((MethodSource) source).getJavaMethod().getAnnotation(GradedTest.class);
                if (gradedTestAnnotation != null) {
                    this.currentGradedTestResult = new GradedTestResult(
                            gradedTestAnnotation.name(),
                            gradedTestAnnotation.number(),
                            gradedTestAnnotation.points(),
                            gradedTestAnnotation.visibility()
                    );
    
                    this.currentGradedTestResult.setScore(gradedTestAnnotation.points());
                }
            }
        }
    
        this.testOutput = new ByteArrayOutputStream();
        System.setOut(new PrintStream(this.testOutput));
    }
    

    The weak link is TestIdentifier.getSource(). The documentation says it gets "the source of the represented test or container, if available." It works for my tests, but I don't know under what circumstances the source is (not) available.