Incorrect ClassLoader used to instantiate test class when using RunWith annotation


(Curtis Mahieu) #1

Gradle JUnit class loader is instantiating the test class object before the @RunWith annotation has the chance to overload the classloader used to instantiate the class:

here is a stack Trace Running With Gradle:

java.lang.ExceptionInInitializerError
 at com.shazam.android.activities.launchers.PhoneFullscreenWebTagLauncherTest.<clinit>(PhoneFullscreenWebTagLauncherTest.java:35)
 at java.lang.Class.forName0(Native Method)
 at java.lang.Class.forName(Class.java:270)
 at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:58)
 at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:49)
 at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:69)
 at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:50)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:606)
 at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
 at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
 at org.gradle.messaging.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
 at org.gradle.messaging.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
 at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
 at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:103)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:606)
 at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
 at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
 at org.gradle.messaging.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:355)
 at org.gradle.internal.concurrent.DefaultExecutorFactory$StoppableExecutorImpl$1.run(DefaultExecutorFactory.java:64)
 at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
 at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
 at java.lang.Thread.run(Thread.java:744)
Caused by: java.lang.RuntimeException: Stub!
 at android.content.Intent.parseUri(Intent.java:39)
 at com.shazam.bean.client.TestAddOns.<clinit>(TestAddOns.java:84)
 ... 29 more

The issue is at “org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:58)”

final Class<?> testClass = Class.forName(testClassName, true, applicationClassLoader);

“true” is being passed to forName, this causes the testClass to be initialized. Unfortunately that causes an issues when a test class is annotated with org.junit.runner.RunWith If you look at “org.junit.internal.builders.AnnotatedBuilder” it will scan the class for RunWith and return the appropriate Runner.

I am trying to use Robolectric as a custom Runner: https://github.com/robolectric/robolectric/blob/master/src/main/java/org/robolectric/RobolectricTestRunner.java This fails because as mentioned above it is unable to use its custom class loader to instantiate the test class.


(Peter Niederwieser) #2

It’s not a general problem with Gradle and ‘@RunWith’ (we use it all the time). Can you elaborate on why this prevents a runner from using its own class loader?


(Curtis Mahieu) #3

Code example:

@RunWIth(RobolectricTestRunner.class)
public class MyTest{
  public final android.content.Intent intent = android.content.Intent.parse("http://www.gradle.com");
  @Test
public void someTest(){
......
}
}

The RobolectricTestRunner class hooks into all the android.* and com.android.* packages and redirects them to its own implementations. Currently the test is failing when loading the class, as Intent.parse has no implementation in the standard Android SDK.

In Maven / IntelliJ the JUnit Executor loads the Class<?> variable pointing to MyClass.class first with “false” so that it can then setup the RunWith Runner before anything is attempted to be initialized in the test class (MyTest).

In the Above example it is currently impossible for the Runner to do anything with member variables before the JUnitTestClassExecuter loads them.


(Peter Niederwieser) #4

Sounds like ‘RobolectricRunner’ makes unsafe assumptions about class loading. Anyway, if using ‘initialize=false’ reliably solves this problem and doesn’t cause new ones, we can probably make it happen. Would you be interested in investigating and providing a pull request?