Skip to content

IsTestableMethod silently ignores @Test annotated methods that return a value #2244

Closed
@george-hawkins

Description

@george-hawkins

IsTestableMethod silently ignores @Test annotated methods that return a value (or are static or are private).

IsTestClassWithTests uses IsTestMethod to discover tests. IsTestMethod is a subclass of IsTestableMethod that sets IsTestableMethod.mustReturnVoid to true.

If you annotate a method with @Test it's clear you want it run as a test. So if you've mistakenly annotated a method that doesn't conform to the requirements of a test then this should result in an error. It shouldn't be just a warning, it should actively fail the test run. However, at the moment such methods are simply silently ignored.

Steps to reproduce

Run the following test class, it contains four tests but only one of them will be run. There is no warning or indication that the other three have been skipped - they are silently ignored despite being annotated with @Test. One is ignored because it returns a value, one because it's static and one because it's private.

package example;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class SilentlyIgnoredTests {
    @Test
    public void testNonIgnored() {
        assertEquals(42, 42);
    }

    @Test
    public boolean testIgnoredDueToReturnType() {
        assertEquals(42, 0);

        return true;
    }

    @Test
    public static void testIgnoredDueToStatic() {
        assertEquals(42, 0);
    }

    @Test
    private void testIgnoredDueToPrivate() {
        assertEquals(42, 0);
    }
}

This issue came up in Kotlin code where the value issue silently turned up when I made what I thought was an unproblematic change. I started with something like this:

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test

class FooTests {
    @Test
    fun `foo test1`() = fooTest {
        assertEquals(42, 42)
    }

    private fun fooTest(block: () -> Unit) {
        block()
    }
}

Obviously fooTest does more than just invoke the passed in lambda but that's the core of it. We had a huge number of tests like this and everything worked fine. Then, in certain cases, I wanted to call fooTest more than once within a single test and I wanted it to return a result that could be checked in such tests (and ignored in all the existing tests). So I made this change, there were no warnings but suddenly many of our tests stopped running. They stopped because the existing tests now no longer returned void.

If you've annotated something with @Test you clearly want it run - it's a mistake if you've used @Test with something that can't be run as a test and it should fail the test run (printing a warning isn't enough - in a huge Maven build these things can get lost).

Context

  • Used versions (Jupiter/Vintage/Platform):
org.junit.jupiter:junit-jupiter-api:jar:5.5.2:compile
org.junit.jupiter:junit-jupiter-api:jar:5.5.2:test
org.junit.jupiter:junit-jupiter-engine:jar:5.5.2:test
org.junit.jupiter:junit-jupiter-params:jar:5.5.2:test
org.junit.jupiter:junit-jupiter:jar:5.5.2:compile
org.junit.jupiter:junit-jupiter:jar:5.5.2:test
org.junit.platform:junit-platform-commons:jar:1.5.2:compile
org.junit.platform:junit-platform-engine:jar:1.5.2:compile
org.junit.platform:junit-platform-launcher:jar:1.5.2:compile
  • Build Tool/IDE:

    • IntelliJ IDEA Community 2019.3.4
    • Apache Maven 3.6.3

Deliverables

I would suggest changing the logic for IsTestableMethod.test to something like this:

@Override
public boolean test(Method candidate) {
    if (!isAnnotated(candidate, this.annotationType)) {
        return false;
    }
    // Beyond this point you know the test annotation is present.

    // Please do not collapse the following into a single statement.
    if (isStatic(candidate)) {
        throw new JUnitException(candidate.getName() + " cannot be static");
    }
    if (isPrivate(candidate)) {
        throw new JUnitException(candidate.getName() + " cannot be private");
    }
    if (isAbstract(candidate)) {
        return false;
    }
    if (returnsVoid(candidate) != this.mustReturnVoid) {
        throw new JUnitException(candidate.getName() + " must return " + (this.mustReturnVoid ? "void" : "a value"));
    }

    return true;
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions