Gleichmut
Gleichmut

Reputation: 6989

Robolectric can not run test with enabled multidex

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"
    }
}

Upvotes: 0

Views: 1269

Answers (1)

David Rawson
David Rawson

Reputation: 21487

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());
    }
}

Upvotes: 3

Related Questions