Khalos Khalos - 1 month ago 10
Java Question

Android Instrumentation Testing - UI Thread Issues

I am trying to write an Instrumentation Test for my Android app.

I'm running into some weird threading issues and I can't seem to find a solution.

My Original Test:

@RunWith(AndroidJUnit4.class)
public class WorkOrderDetailsTest {

@Rule
public ActivityTestRule<WorkOrderDetails> activityRule = new ActivityTestRule<>(WorkOrderDetails.class);

@Test
public void loadWorkOrder_displaysCorrectly() throws Exception {
final WorkOrderDetails activity = activityRule.getActivity();

WorkOrder workOrder = new WorkOrder();
activity.updateDetails(workOrder);

//Verify customer info is displayed
onView(withId(R.id.customer_name))
.check(matches(withText("John Smith")));
}
}


This resulted in an


android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

...

com.kwtree.kwtree.workorder.WorkOrderDetails.updateDetails(WorkOrderDetails.java:155)


The only thing the
updateDetails()
method does is some
setText()
calls.

After researching a bit, it seemed like adding a
UiThreadTestRule
and
android.support.test.annotation.UiThreadTest
annotation to my test would fix the problem.

@UiThreadTest:

@RunWith(AndroidJUnit4.class)
public class WorkOrderDetailsTest {

//Note: This is new
@Rule
public UiThreadTestRule uiThreadTestRule = new UiThreadTestRule();

@Rule
public ActivityTestRule<WorkOrderDetails> activityRule = new ActivityTestRule<>(WorkOrderDetails.class);

@Test
@UiThreadTest //Note: This is new
public void loadWorkOrder_displaysCorrectly() throws Exception {
final WorkOrderDetails activity = activityRule.getActivity();

WorkOrder workOrder = new WorkOrder();
activity.updateDetails(workOrder);

//Verify customer info is displayed
onView(withId(R.id.customer_name))
.check(matches(withText("John Smith")));
}
}



java.lang.IllegalStateException: Method cannot be called on the main application thread (on: main)


(Note: All of the methods in this stack trace are not my code)

It seems to be giving me mixed results... If it needs to be run on the original thread that created the views but can't run on the main thread, what thread should it be run on?

I'd really appreciate any help or suggestions!

Answer

Those instrumentation tests run inside their own app. This also means, they run in their own thread.

You must think of your instrumentation as something you install alongside your actual app, so your possible interactions are 'limited'.

You need to call all view methods from the UIThread / main thread of the application, so calling activity.updateDetails(workOrder); from your instrumentation thread is not the application main thread. This is why the exception is thrown.

You can just run the code you need to test on your main thread like you would do if you were calling it inside your app from a different thread by using

activity.runOnUiThread(new Runnable() {
    public void run() {
        activity.updateDetails(workOrder);
    }
}

With this running your test should work.

The illegal state exception you are receiving seems to be because of your interaction with the rule. The documentation states

Note that instrumentation methods may not be used when this annotation is present.

If you start / get your activity in @Before it should also work.