【问题标题】:ActivityUnitTestCase throws RuntimeException when ran with AndroidJUnitRunnerActivityUnitTestCase 在使用 AndroidJUnitRunner 运行时抛出 RuntimeException
【发布时间】:2015-03-15 11:52:39
【问题描述】:

我正在尝试将Espresso 2.0's AndoridJUnitRunner 与 ActivityUnitTestCase 集成。但是,当startActivity() tries to initialize mMockParent = new MockParent() 时,我的测试崩溃了。

这就是我所做的:

使用 Intellij 14 CE 创建一个新项目并对 build.gradle 进行一些更改。

android{
    defaultConfig {
        applicationId "com.noob.testing"
        minSdkVersion 9
        targetSdkVersion 21
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }
}
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:21.0.+'

    androidTestCompile('org.mockito:mockito-core:1.9.5')
    androidTestCompile('com.google.dexmaker:dexmaker:1.2')
    androidTestCompile('com.google.dexmaker:dexmaker-mockito:1.2')

    androidTestCompile('com.android.support.test.espresso:espresso-core:2.0')
    androidTestCompile('com.android.support.test:testing-support-lib:0.1')
}

编写一个 JUnit4 风格的单元测试。

@RunWith(AndroidJUnit4.class)
public class MainActivityJUnit4Test extends ActivityUnitTestCase<MainActivity> {
    public MainActivityJUnit4Test() {
        super(MainActivity.class);
    }

    MainActivity activity;

    @Before
    public void setup() throws Exception {
        injectInstrumentation(InstrumentationRegistry.getInstrumentation());
        super.setUp();
        ContextThemeWrapper context = new ContextThemeWrapper(getInstrumentation().getTargetContext(), R.style.AppTheme);
        setActivityContext(context);
        activity = startActivity(new Intent(Intent.ACTION_MAIN), null, null);
    }

    @Test
    public void baseCase() {
        TextView tv = (TextView) activity.findViewById(R.id.tv);
        Assert.assertEquals("Hello World", tv.getText());

    }
}

运行测试,获取堆栈跟踪。

junit.framework.AssertionFailedError
at junit.framework.Assert.fail(Assert.java:48)
at junit.framework.Assert.assertTrue(Assert.java:20)
at junit.framework.Assert.assertNotNull(Assert.java:218)
at junit.framework.Assert.assertNotNull(Assert.java:211)
at android.test.ActivityUnitTestCase.startActivity(ActivityUnitTestCase.java:147)
at com.sdchang.testing.MainActivityJUnit4Test.setup(MainActivityJUnit4Test.java:32)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:525)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:27)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:24)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
at org.junit.runner.JUnitCore.run(JUnitCore.java:157)
at org.junit.runner.JUnitCore.run(JUnitCore.java:136)
at android.support.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:270)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1701)

这个堆栈跟踪实际上是对

的重新抛出
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

startActivity() tries to initialize mMockParent = new MockParent() 时发生:

            ComponentName cn = new ComponentName(mActivityClass.getPackage().getName(), 
                    mActivityClass.getName());
            intent.setComponent(cn);
            ActivityInfo info = new ActivityInfo();
            CharSequence title = mActivityClass.getName();
            mMockParent = new MockParent();
            String id = null;

为了让 AndroidJUnitRunner 与 ActivityUnitTestCase 一起工作,我还有什么遗漏吗?任何帮助将不胜感激。

【问题讨论】:

    标签: android unit-testing junit android-espresso activityunittestcase


    【解决方案1】:

    在用头撞墙、窗户和各种家具后,我终于完成了测试!

    a response on its issue tracker,我了解到不能从检测线程创建新的处理程序。这意味着 startActivity() 必须在 UI 线程中调用。这导致了 3 个解决方案。

    1.使用 Instrumentation 的runOnMainSync()

    getInstrumentation().runOnMainSync(new Runnable() {
        @Override
        public void run() {
            activity = startActivity(new Intent(Intent.ACTION_MAIN), null, null);
        }
    });
    

    2.创建自己的扩展ActivityUnitTestCase并覆盖startActivity的基类

    @Override
    protected T startActivity(final Intent intent, final Bundle savedInstanceState,
            final Object lastNonConfigurationInstance) {
        return startActivityOnMainThread(intent, savedInstanceState, lastNonConfigurationInstance);
    }
    
    private T startActivityOnMainThread(final Intent intent, final Bundle savedInstanceState,
            final Object lastNonConfigurationInstance) {
        final AtomicReference<T> activityRef = new AtomicReference<>();
        final Runnable activityRunnable = new Runnable() {
            @Override
            public void run() {
                activityRef.set(YourBaseActivityUnitTestCase.super.startActivity(
                        intent, savedInstanceState, lastNonConfigurationInstance));
            }
        };
    
        if (Looper.myLooper() != Looper.getMainLooper()) {
            getInstrumentation().runOnMainSync(activityRunnable);
        } else {
            activityRunnable.run();
        }
    
        return activityRef.get();
    }
    

    3.如果您在测试方法中调用 startActivity() 并且您的整个测试可以在主线程上运行,您可以简单地使用 @UiThreadTest 注释您的测试方法。

    在我的测试中,我尝试了所有 3 种解决方案,但只有 1 和 2 可以工作

    【讨论】:

    • 我没有对此进行测试,但也许 3 不起作用,因为@UiThreadTest 似乎只适用于扩展InstrumentationTestCase 的测试,而不适用于ActivityUnitTestCaseAndroid Dev Reference
    • 顺便说一句,感谢您间接回答了我的一个问题,这就是为什么我需要在我的 ActivityUnitTestCase 中完全使用 runOnMainSync 的原因,答案是我最终添加了其他子类化 @987654329 的测试@,我安装了 Espresso,这需要将我的 build.gradle 文件中的 testInstrumentationRunnerandroid.test.InstrumentationTestRunner 切换到 android.support.test.runner.AndroidJUnitRunner,这改变了之前能够直接调用 UI 相关方法的行为。
    猜你喜欢
    • 2016-12-28
    • 2014-09-27
    • 2011-08-20
    • 1970-01-01
    • 1970-01-01
    • 2015-01-25
    • 2023-03-24
    • 1970-01-01
    • 2018-07-10
    相关资源
    最近更新 更多