4

According to the Espresso documentation an instrumentation test should automatically wait for AsyncTasks to finish. But it does not work. I've created this simple test case:

package foo.bar;

import android.os.AsyncTask;
import android.support.test.annotation.UiThreadTest;
import android.support.test.filters.LargeTest;
import android.support.test.rule.UiThreadTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.util.Log;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static org.junit.Assert.assertEquals;

@RunWith(AndroidJUnit4.class)
@LargeTest
public class ExampleInstrumentedTest {

    private static final String TAG = "ExampleInstrumentedTest";

    @Rule public UiThreadTestRule uiThreadTestRule = new UiThreadTestRule();

    @Test
    @UiThreadTest
    public void testAsyncTask() throws Throwable {
        Log.d(TAG, "testAsyncTask entry");
        uiThreadTestRule.runOnUiThread(() -> new AsyncTask<String, Void, Integer>() {
            @Override
            protected Integer doInBackground(String... params) {
                Log.d(TAG, "doInBackground() called with: params = [" + params + "]");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException ignored) {
                }
                return params.length;
            }

            @Override
            protected void onPostExecute(Integer integer) {
                Log.d(TAG, "onPostExecute() called with: integer = [" + integer + "]");
                assertEquals(3, (int) integer);
                throw new RuntimeException("this should fail the test");
            }
        }.execute("One", "two", "three"));
        Log.d(TAG, "testAsyncTask end");
    }
}

The test should fail when returning to the UI Thread but it always succeeds. This is the logcat output of the test:

I/TestRunner: started: testAsyncTask(foo.bar.ExampleInstrumentedTest)
D/ExampleInstrumentedTest: testAsyncTask entry
D/ExampleInstrumentedTest: testAsyncTask end
I/TestRunner: finished: testAsyncTask(foo.bar.ExampleInstrumentedTest)
D/ExampleInstrumentedTest: doInBackground() called with: params = [[Ljava.lang.String;@8da3e9]

As you can see the test finishes before the background method is even executed. How can I make the test to wait for it?

McFarlane
  • 1,777
  • 2
  • 22
  • 39

3 Answers3

8

Turns out that Espresso does wait for AsyncTasks to finish but only if there is a view interaction.

The reason is that Espresso waits for the task during the UiController#loopMainThreadUntilIdle() method which is automatically called behind the curtains on every view interaction. So despite my test does not need any views or activities, i had to create them.

This is how the working test now looks like:

@RunWith(AndroidJUnit4.class)
@LargeTest
public class ExampleInstrumentedTest {

    private static final String TAG = "ExampleInstrumentedTest";

    @Rule public ActivityTestRule<TestingActivity> activityTestRule = new ActivityTestRule<>(
        TestingActivity.class, false, false);

    @Before
    public void setUp() throws Exception {
        activityTestRule.launchActivity(new Intent());
    }

    @Test
    public void testAsyncTask() throws Throwable {
        Log.d(TAG, "testAsyncTask entry");

        AsyncTask<String, Void, Integer> task = new AsyncTask<String, Void, Integer>() {

            @Override
            protected Integer doInBackground(String... params) {
                Log.d(TAG, "doInBackground() called with: params = [" + params + "]");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException ignored) {
                }
                return params.length;
            }

            @Override
            protected void onPostExecute(Integer integer) {
                Log.d(TAG, "onPostExecute() called with: integer = [" + integer + "]");
                assertEquals(3, (int) integer);
                throw new RuntimeException("this should fail the test");
            }
        };
        task.execute("One", "two", "three");
        Espresso.onView(withId(android.R.id.content)).perform(ViewActions.click());

        Log.d(TAG, "testAsyncTask end");
    }
}

The most important new line is: Espresso.onView(withId(android.R.id.content)).perform(ViewActions.click()); as it causes Espresso to wait for the AsyncTask background operation to finish.

TestingActivity is just an empty Activity:

public class TestingActivity extends Activity {

}
McFarlane
  • 1,777
  • 2
  • 22
  • 39
0

Have you tried using Espresso.onIdle()?

From the docs:

Loops the main thread until the app goes idle.

Only call this method for tests that do not interact with any UI elements, but require Espresso's main thread synchronisation! This method is mainly useful for test utilities and frameworks that are build on top of Espresso.

Community
  • 1
  • 1
tinsukE
  • 930
  • 9
  • 20
-2

It is working as intended. Since you are calling Thread.sleep() inside the doInBackground() it does not sleep the UI Thread. Move that sleeping code to onPostExecute and it should work as expected.

Murat Karagöz
  • 35,401
  • 16
  • 78
  • 107
  • As you can see from the log messages it is not working as intended. The doInBackground() method is called after the test has finished and onPostExecute() is not even called. Espresso should wait for the background operation to finish before finishing the test. This can be any long running task which can be emulated by Thread.sleep(). – McFarlane Feb 16 '17 at 15:13
  • @McFarlane Did you do as I suggested? – Murat Karagöz Feb 16 '17 at 15:14
  • I did not since this would not reflect the test case. Imagine Thread.sleep replaced by a network operation. You can't run these from onPostExecute. – McFarlane Feb 16 '17 at 15:16
  • @McFarlane If you would do that you could see that it works and conclude that Espresso is not waiting for Idling Resources outside the UI Thread. Next time instead of down voting mindlessly you could try the suggested answer and see how it goes from there. – Murat Karagöz Feb 16 '17 at 15:19
  • I've not downvoted you because your proposal does not work but because it does not answer the question. Quoting Espresso docs: `The centerpiece of Espresso is its ability to seamlessly synchronize all test operations with the application being tested. By default, Espresso waits for UI events in the current message queue to be handled and for default AsyncTasks to complete before it moves on to the next test operation.` So if my usage does not count as _default usage_: `How can I make the test to wait for it?` Your proposal eliminated the need for any async operation which is not what I need. – McFarlane Feb 16 '17 at 15:31
  • Did not see your question at the bottom. I asnwered to your assumption. Basically what you want to do is keeping the UI Thread busy while doing the network call. One way could be this http://stackoverflow.com/a/30820189/4467208 – Murat Karagöz Feb 16 '17 at 15:35