0

I am writing unit tests to cover API with tests. I use robolectric and gradle, also have to add multidex to support large apk build. Unexpectedly I can not run the test and can not understand the stacktrace

java.lang.NullPointerException: parentLoader == null && !nullAllowed
at java.lang.ClassLoader.<init>(ClassLoader.java:210)
at java.lang.ClassLoader.<init>(ClassLoader.java:202)
at java.security.SecureClassLoader.<init>(SecureClassLoader.java:48)
at java.net.URLClassLoader.<init>(URLClassLoader.java:710)
at java.net.URLClassLoader.<init>(URLClassLoader.java:555)
at org.codehaus.classworlds.RealmClassLoader.<init>(RealmClassLoader.java:94)
at org.codehaus.classworlds.RealmClassLoader.<init>(RealmClassLoader.java:83)
at org.codehaus.classworlds.DefaultClassRealm.<init>(DefaultClassRealm.java:116)
at org.codehaus.classworlds.ClassWorld.newRealm(ClassWorld.java:100)
at org.apache.maven.artifact.ant.AbstractArtifactTask.getContainer(AbstractArtifactTask.java:490)
at org.apache.maven.artifact.ant.AbstractArtifactTask.lookup(AbstractArtifactTask.java:457)
at org.apache.maven.artifact.ant.AbstractArtifactTask.initSettings(AbstractArtifactTask.java:290)
at org.apache.maven.artifact.ant.AbstractArtifactTask.execute(AbstractArtifactTask.java:750)
at org.robolectric.internal.dependency.MavenDependencyResolver.getLocalArtifactUrls(MavenDependencyResolver.java:40)
at org.robolectric.internal.dependency.CachedDependencyResolver.getLocalArtifactUrls(CachedDependencyResolver.java:43)
at org.robolectric.internal.InstrumentingClassLoaderFactory.getSdkEnvironment(InstrumentingClassLoaderFactory.java:39)
at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:187)
at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:54)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:152)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at android.support.test.internal.runner.TestExecutor.execute(TestExecutor.java:59)
at android.support.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:262)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1837)



@RunWith(RobolectricGradleTestRunner.class)
@Config(application = TestApplication.class,
    constants = BuildConfig.class,
    sdk = 21)
public class ApiTest {

private MockWebServer webServer;
private IApi api;

private TestUtils testUtils;

@Before
public void setUp() throws Exception {
    Log.d(App.TAG, "Test setUp");
    testUtils = new TestUtils();
    webServer = new MockWebServer();
    webServer.start();
    webServer.setDispatcher(new Dispatcher() {
        @Override
        public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
            MockResponse response;
            if (request.getPath().equals("savingsgoals")) {
                response = new MockResponse().setResponseCode(200)
                        .setBody(testUtils.readString("json/goals.json"));
            } else if (request.getPath().equals("savingsgoals/" + TestApp.Const.GOAL_ID + "/feed")) {
                response = new MockResponse().setResponseCode(200)
                        .setBody(testUtils.readString("json/feed.json"));
            } else if (request.getPath().equals("savingsrules")) {
                response = new MockResponse().setResponseCode(200)
                        .setBody(testUtils.readString("json/rules.json"));
            } else if (request.getPath().equals("user/" + TestApp.Const.USER_ID)) {
                response = new MockResponse().setResponseCode(200)
                        .setBody(testUtils.readString("json/user.json"));
            } else {
                response = new MockResponse().setResponseCode(404);
            }
            return response;
        }
    });
    HttpUrl url = webServer.url("/");
    api = ApiModule.getApiInterface(url.toString());
}

@Test
public void testGoals() throws Exception {
    TestSubscriber<SavingsGoals> testSubscriber = new TestSubscriber();
    api.getSavingsGoals().subscribe(testSubscriber);

    testSubscriber.assertNoErrors();
    testSubscriber.assertValueCount(1);

    SavingsGoals goals = testSubscriber.getOnNextEvents().get(0);
    goals.getSavingsGoals();
}

}

Do you have an idea what is root cause?

Here is my gradle build

androidTestCompile 'org.robolectric:robolectric:3.3.2'
androidTestCompile('com.squareup.okhttp:mockwebserver:2.7.0', {
    exclude group: 'com.squareup.okio', module: 'okio'
})
androidTestCompile 'junit:junit:4.12'

UPDATE

Console output:

Error:Execution failed for task ':app:transformClassesWithDexForDebugAndroidTest'. > com.android.build.api.transform.TransformException: java.lang.RuntimeException: java.lang.RuntimeException: Unable to pre-dex 'D:\Users\John\.gradle\caches\modules-2\files-2.1\com.thoughtworks.xstream\xstream\1.4.8\520d90f30f36a0d6ba2dc929d980831631ad6a92\xstream-1.4.8.jar' to 'C:\workspace\TestRobolectricTest\app\build\intermediates\pre-dexed\androidTest\debug\xstream-1.4.8_54d21a03bcf95b493d9c102453945dde45691be3.jar'

allprojects {
    tasks.withType(JavaCompile) {
        sourceCompatibility = "1.8"
        targetCompatibility = "1.8"
    }
}
Gleichmut
  • 5,953
  • 5
  • 24
  • 32
  • You're not using the latest version of Robolectric. Try upgrading to the latest – David Rawson Apr 23 '17 at 19:10
  • In fact many of those dependencies are outdated, Dagger is already at `2.10` and Mockito has reached version `2.7.22`. I would suggest trying to spend some time updating the dependencies and then seeing if you can reproduce the problem. Once you have done that, another thing to try is deleting the `.m2` folder which caches the Robolectric dependencies (downloaded from Maven at runtime) and try a `gradlew --refresh-dependencies`. You can get errors like that from mismatched .jars – David Rawson Apr 24 '17 at 05:18
  • It took time to return to this topic. I updated all dependencies and clean the cache. Still can't run code, but now with another issue (see update section), seems some module's bytecode is uncompatible with others (java 1.7 vs 1.8). I applied task which should give the same bytecode as output, but issue remains. Do you have any thoughts why does it happen? – Gleichmut Apr 29 '17 at 10:17
  • Good job updating the dependencies. Can you try this answer here http://stackoverflow.com/a/33653063/5241933 – David Rawson Apr 29 '17 at 10:38
  • Didn't help, the same issue. I narrowed the scope of possible issue to just few dependecies -- adding robolectric creates an issue. Could you please create sample project on your side and make attempt to run tests with this two dependencies? Just want to confirm it is not an issue with my environment – Gleichmut Apr 29 '17 at 13:09
  • Sure I'll try the sample project later today – David Rawson Apr 29 '17 at 20:45
  • Thank you for attempt to help. Did you succeed? – Gleichmut Apr 30 '17 at 06:30
  • Just trying it now – David Rawson Apr 30 '17 at 06:43
  • Hey I just noticed something - you should not use Robolectric in `androidTestCompile` you should only use it in `testCompile` see [getting started](http://robolectric.org/getting-started/) – David Rawson Apr 30 '17 at 06:45

1 Answers1

3

Please make sure you use Robolectric with the test (not androidTest) folders and the dependency should be likewise set with testCompile:

 testCompile 'org.robolectric:robolectric:3.3.2' //don't use androidTestCompile here!!!

If you have problems with Robolectric, some steps to try include:

  1. Delete the .m2 folder to cause the dependencies from maven to be downloaded again
  2. Try refreshing gradle dependencies gradlew --refresh-dependencies and cleaning and rebuilding the project
  3. Make sure you have the latest jdk and delete the old jdks on your machine
  4. Make sure your tests are configured to run with the correct project dir
  5. Try adjusting your Robolectric test parameters:

    @RunWith(RobolectricGradleTestRunner.class)
    @Config(application = TestApplication.class,
    constants = BuildConfig.class,
    sdk = 21)
    

You can try using RobolectricTestRunner.class instead of RobolectricGradleTestRunner.class and leaving out the @Config annotation. If that doesn't work, you can create a class:

public class EmptyApplication extends Application {
}

And use that:

@Config(application = EmptyApplication.class)

Update: note the difference between a Robolectric test (in the test folder) and an Instrumented Android test (in the androidTest folder). Robolectric tests are tests run in the IDE (on your computer's JVM) that simulate the Android classes (e.g., Context) available on a real device. Instrumented Android tests run on an Android handset or an emulator and use the real Context etc. from the Android SDK. The following is the example of an Instrumented test from an empty project:

package example.github.com.robolectricbug;

import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;

import static org.junit.Assert.*;

/**
 * Instrumentation test, which will execute on an Android device.
 *
 * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
 */
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
    @Test
    public void useAppContext() throws Exception {
        // Context of the app under test.
        Context appContext = InstrumentationRegistry.getTargetContext();

        assertEquals("example.github.com.robolectricbug", appContext.getPackageName());
    }
}
David Rawson
  • 20,912
  • 7
  • 88
  • 124
  • Thank you for help me identify the cause. However I still have the issue. How I can test then robolectric with Android API? testCompile will work only for test which run on JVM, but not on Android emulator -- it wouldn't be possible to test code with Android API. http://stackoverflow.com/questions/29021331/confused-about-testcompile-and-androidtestcompile-in-android-gradle – Gleichmut May 01 '17 at 04:20
  • @Gleichmut That's the point of Robolectric - it will allow you to run tests that would normally have to run on the emulator inside the IDE instead. If you want to run tests on the emulator or on a device, you wouldn't need to use Robolectric. You could just right a normal JUnit test that would run on the device – David Rawson May 01 '17 at 04:21
  • @Gleichmut I updated my answer. Please let me know if there are any more questions. If the answer is helpful, do you mind accepting it? – David Rawson May 01 '17 at 04:28
  • Thank you (sorry for delay with acceptance, just run into another problem with it) – Gleichmut May 01 '17 at 10:18