-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Description
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;
}