Does not work on JDK 16+ due to JEP-396 (strong encapsulation)
de.jodamob.kotlin.testrunner.NoMoreFinalsClassLoader uses Javassist which tries to make protected java.lang.ClassLoader.defineClass accessible. Due to JEP-396, this is forbidden since JDK 16 and cannot be overriden by any --add-opens call either.
Please update the product in order to use another API.
FYI, I use Spock and my workaround for ignoring tests using SpotlinTestRunner on JDK 16+ looks as follows, using a delegation pattern:
package de.scrum_master.testing
import de.jodamob.kotlin.testrunner.SpotlinTestRunner
import org.junit.runner.Description
import org.junit.runner.manipulation.Filter
import org.junit.runner.manipulation.NoTestsRemainException
import org.junit.runner.manipulation.Sorter
import org.junit.runner.notification.RunNotifier
import org.junit.runners.model.InitializationError
import org.spockframework.runtime.Sputnik
class JEP396AwareSpotlinTestRunner extends Sputnik {
private static final int javaMajor = Integer.parseInt(System.properties.getProperty("java.version").split("[.]")[0])
public static final boolean hasJEP396 = javaMajor >= 16
private static final Filter ignoreAllFilter = new Filter() {
@Override
boolean shouldRun(Description description) { return false }
@Override
String describe() { return "ignores all tests" }
}
private final Sputnik sputnik
JEP396AwareSpotlinTestRunner(Class<?> clazz) throws InitializationError {
super(clazz)
sputnik = hasJEP396 ? this : new SpotlinTestRunner(clazz)
}
@Override
void filter(Filter filter) throws NoTestsRemainException {
if (hasJEP396)
super.filter(ignoreAllFilter)
else
sputnik.filter(filter)
}
@Override
void run(RunNotifier notifier) {
if (hasJEP396)
super.run(notifier)
else
sputnik.run(notifier)
}
@Override
Description getDescription() {
if (hasJEP396)
return super.getDescription()
else
return sputnik.getDescription()
}
@Override
void sort(Sorter sorter) {
if (hasJEP396)
super.sort(sorter)
else
sputnik.sort(sorter)
}
}
The Spock spec still needs an @IgnoreIf or @Requires in addition to @RunWith(JEP396AwareSpotlinTestRunner), but otherwise no changes are required. It can even utilise a constant JEP396AwareSpotlinTestRunner.hasJEP396 for its own condition:
package de.scrum_master.stackoverflow
import de.jodamob.kotlin.testrunner.OpenedClasses
import de.jodamob.kotlin.testrunner.OpenedPackages
import de.scrum_master.testing.JEP396AwareSpotlinTestRunner
import org.junit.runner.RunWith
import spock.lang.IgnoreIf
import spock.lang.Specification
/**
* See https://stackoverflow.com/q/48391716/1082681
* See https://github.com/dpreussler/kotlin-testrunner
*/
@RunWith(JEP396AwareSpotlinTestRunner)
@IgnoreIf({ JEP396AwareSpotlinTestRunner.hasJEP396 })
@OpenedClasses(FinalClass)
//@OpenedPackages("de.scrum_master.stackoverflow")
class AnotherClassSpotlinRunnerTest extends Specification {
def "use SpotlinRunner to stub final method in final class"() {
given:
FinalClass finalClass = Stub() {
finalMethod() >> "mocked"
}
expect:
new AnotherClass().doSomething(finalClass) == "mocked"
}
}
The workaround is necessary, because JUnit 4 runners are initialised very early in the lifecycle, even before SpockConfig.groovy or global Spock extensions, even more so before @IgnoreIf and @Requires can kick in.
This is ugly, of course. There are alternatives to the Kotlin test runner, e.g. my own tool Sarek which can do a lot of fancy things, unfinalising classes for JUnit 4/5, TestNG and Spock just being one of them. But before I developed it, I was using your tool in Spock 1.3 and today just noticed that my old example code was no longer when running on JDK 16. So I thought, I take some time to create this issue, FWIW.