【问题标题】:Robolectric can not run test with enabled multidexRobolectric 无法在启用 multidex 的情况下运行测试
【发布时间】:2017-04-23 11:22:19
【问题描述】:

我正在编写单元测试来覆盖带有测试的 API。我使用 robolectric 和 gradle,还必须添加 multidex 以支持大型 apk 构建。没想到无法运行测试,也看不懂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();
}

}

您知道根本原因是什么吗?

这是我的 gradle 构建

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'

更新

控制台输出:

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

【问题讨论】:

  • 您使用的不是最新版本的 Robolectric。尝试升级到最新版本
  • 事实上很多依赖已经过时了,Dagger 已经在2.10 并且Mockito 已经达到了2.7.22 版本。我建议尝试花一些时间更新依赖项,然后看看是否可以重现问题。完成此操作后,要尝试的另一件事是删除缓存 Robolectric 依赖项的 .m2 文件夹(在运行时从 Maven 下载)并尝试 gradlew --refresh-dependencies。你可以从不匹配的 .jars 中得到类似的错误
  • 花时间回到这个话题。我更新了所有依赖项并清理了缓存。仍然无法运行代码,但现在有另一个问题(请参阅更新部分),似乎某些模块的字节码与其他模块不兼容(java 1.7 vs 1.8)。我应用了应该提供与输出相同的字节码的任务,但问题仍然存在。你有什么想法为什么会发生?
  • 很好地更新了依赖项。你能在这里试试这个答案吗stackoverflow.com/a/33653063/5241933
  • 没有帮助,同样的问题。我将可能问题的范围缩小到几个依赖项——添加 robolectric 会产生问题。您能否在您身边创建示例项目并尝试使用这两个依赖项运行测试?只是想确认这不是我的环境的问题

标签: android automated-tests robolectric dagger-2


【解决方案1】:

请确保您将 Robolectric 与 testnot androidTest)文件夹一起使用,并且同样应使用 testCompile 设置依赖关系:

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

如果您在使用 Robolectric 时遇到问题,可以尝试以下步骤:

  1. 删除.m2文件夹以重新下载maven的依赖项
  2. 尝试刷新 gradle 依赖项gradlew --refresh-dependencies 并清理和重建项目
  3. 确保您拥有最新的 jdk 并删除计算机上的旧 jdk
  4. 确保您的测试配置为使用正确的项目目录运行
  5. 尝试调整您的 Robolectric 测试参数:

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

您可以尝试使用RobolectricTestRunner.class 代替RobolectricGradleTestRunner.class 并省略@Config 注释。如果这不起作用,您可以创建一个类:

public class EmptyApplication extends Application {
}

然后使用它:

@Config(application = EmptyApplication.class)

更新:注意 Robolectric 测试(在 test 文件夹中)和 Instrumented Android 测试(在 androidTest 文件夹中)之间的区别。 Robolectric 测试是在 IDE(在您的计算机的 JVM 上)中运行的测试,模拟真实设备上可用的 Android 类(例如,Context)。仪表化的 Android 测试在 Android 手机或模拟器上运行,并使用 Android SDK 中的 real Context 等。以下是来自空项目的 Instrumented 测试示例:

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

【讨论】:

  • 感谢您帮助我找出原因。但是我仍然有这个问题。我如何使用 Android API 测试然后 robolectric? testCompile 仅适用于在 JVM 上运行的测试,但不适用于 Android 模拟器 - 无法使用 Android API 测试代码。 stackoverflow.com/questions/29021331/…
  • @Gleichmut 这就是 Robolectric 的重点——它将允许您运行通常必须在 IDE 内的模拟器上运行的测试。如果你想在模拟器或设备上运行测试,你不需要使用 Robolectric。您可以在设备上运行正常的 JUnit 测试
  • @Gleichmut 我更新了我的答案。如果还有其他问题,请告诉我。如果答案有帮助,您介意接受吗?
  • 谢谢(很抱歉延迟接受,只是遇到另一个问题)
猜你喜欢
  • 2019-08-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-06
  • 2023-01-09
  • 1970-01-01
相关资源
最近更新 更多