ActivityUnitTestCase throws RuntimeException when ran with AndroidJUnitRunner
Asked Answered
G

1

13

I am trying to integrate Espresso 2.0's AndoridJUnitRunner with ActivityUnitTestCase. However, my tests are crashing when startActivity() tries to initialize mMockParent = new MockParent().

Here's what I did:

Create a new project with Intellij 14 CE and make some changes to 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')
}

Write a JUnit4-style unit test.

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

    }
}

Run the test, getting a stack trace.

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)

This stack trace is actually a rethrow of

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

that occurs when 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;

Am I missing anything else to get AndroidJUnitRunner working with ActivityUnitTestCase? Any help would be greatly appreciated.

Geez answered 16/1, 2015 at 19:28 Comment(0)
G
24

After banging my head against the wall, windows and various furniture, I finally got my tests working!

From a response on its issue tracker, I learned that no new Handlers can be created from the instrumentation thread. Which means startActivity() must be invoked in the UI thread. Which leads to 3 solutions.

1.Use Instrumentation's runOnMainSync()

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

2.Create your own base class that extends ActivityUnitTestCase and override 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.If you call startActivity() in a test method and your entire test can run on the main thread, you can simply annotate your test method with @UiThreadTest.

In my testing, I've tried all 3 solutions, but only 1 & 2 will work.

Geez answered 6/2, 2015 at 10:42 Comment(2)
I haven't tested this, but perhaps 3 does not work because @UiThreadTest seems to only apply to tests extending InstrumentationTestCase, not ActivityUnitTestCase? Android Dev ReferenceLyse
By the way, thanks for indirectly answering a question I had, which is why I needed to bother with runOnMainSync at all in my ActivityUnitTestCase, and the answer is that I eventually added other tests that subclassed ActivityInstrumentationTestCase2, and I installed Espresso, and this required switching the testInstrumentationRunner in my build.gradle file from android.test.InstrumentationTestRunner to android.support.test.runner.AndroidJUnitRunner, which alters the previous behavior of being able to directly call UI-related methods.Lyse

© 2022 - 2024 — McMap. All rights reserved.