getActivity() call causes RuntimeException: Could

2019-03-15 06:35发布

问题:

Updated #1: more info added to the end of this post

I'm new to Android development and testing.

I have three Espresso tests. First test passes, but the second one will not run because the call to getActivity() in setUp() method before the second test always fails:

Blockquote java.lang.RuntimeException: Could not launch intent Intent { act=android.intent.action.MAIN flg=0x10000000 cmp=my.packagename/.ActivityMain } within 45 seconds. ...

The third test passes.

I don't have any long running operations, animations or network calls on creation. I can click through all menu items in my app manually repeating the test flow without issues.

For some reason the first test "breaks" next getActivity() call in setUp() before the second test. All following tests (after the second test) will run OK.

I found similar question but it looks like it wasn't resolved and it has a little bit different problem.

Test code:

import static com.google.android.apps.common.testing.ui.espresso.Espresso.onData;
import static com.google.android.apps.common.testing.ui.espresso.Espresso.onView;
import static com.google.android.apps.common.testing.ui.espresso.Espresso.openActionBarOverflowOrOptionsMenu;
import static com.google.android.apps.common.testing.ui.espresso.action.ViewActions.click;
import static com.google.android.apps.common.testing.ui.espresso.assertion.ViewAssertions.matches;
import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.isDisplayed;
import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.withId;
import static com.google.android.apps.common.testing.ui.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import net.humblegames.bodylasticscalculator.ActivityMain;
import net.humblegames.bodylasticscalculator.R;
import net.humblegames.bodylasticscalculator.applogic.BandSystem;

import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.junit.After;
import org.junit.Before;

import android.app.Activity;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;

public class MenuNavigationTestExperiment extends
        ActivityInstrumentationTestCase2<ActivityMain> {

    private String TAG = getClass().getSimpleName();
    private Activity activity;

    public MenuNavigationTestExperiment() {
        super(ActivityMain.class);
    }


    @Before
    public void setUp() throws Exception {
        Log.d(TAG, "SETUP");
        activity = getActivity();

        super.setUp();
    }

    @After
    public void tearDown() throws Exception {
        Log.d(TAG, "TEARDOWN");

        super.tearDown();
    }


    public void testFirst() {
        Log.d(TAG, "testFirst");

        clickMenuItem("Select band system");
        onData(allOf(is(instanceOf(BandSystem.class)), hasName("MMA Training")))
        .onChildView(withId(R.id.label)).perform(click());

        clickMenuItem("About");
        onView(withId(R.id.about_webview)).check(matches(isDisplayed()));
    }

    public void testSecond() {
        Log.d(TAG, "testSecond");
        // this test will not run
    }


    public void testThird() {
        Log.d(TAG, "testThird");
        // this test will pass
    }

    // ------------------ HELPER METHODS ---------------------------------------

    private void clickMenuItem(String menuItem) {
        Log.d(TAG, "clickMenuItem");

        openActionBarOverflowOrOptionsMenu(getInstrumentation()
                .getTargetContext());

        onView(withText(menuItem)).perform(click());
    }

    private Matcher<BandSystem> hasName(final String name) {
        Log.d(TAG, "hasName");

        return new BaseMatcher<BandSystem>() {
            @Override
            public boolean matches(final Object item) {
                final BandSystem foo = (BandSystem) item;
                return name == foo.getName();
            }

            @Override
            public void describeTo(final Description description) {
                description.appendText("getName should return ").appendValue(
                        name);
            }
        };
    }

}

Error trace:

java.lang.RuntimeException: Could not launch intent Intent { act=android.intent.action.MAIN flg=0x10000000 cmp=net.humblegames.bodylasticscalculator/.ActivityMain } within 45 seconds. Perhaps the main thread has not gone idle within a reasonable amount of time? There could be an animation or something constantly repainting the screen. Or the activity is doing network calls on creation? See the threaddump logs. For your reference the last time the event queue was idle before your activity launch request was 1395582828351 and and now the last time the queue went idle was: 1395582830169. If these numbers are the same your activity might be hogging the event queue.
at com.google.android.apps.common.testing.testrunner.GoogleInstrumentation.startActivitySync(GoogleInstrumentation.java:277)
at android.test.InstrumentationTestCase.launchActivityWithIntent(InstrumentationTestCase.java:119)
at android.test.InstrumentationTestCase.launchActivity(InstrumentationTestCase.java:97)
at android.test.ActivityInstrumentationTestCase2.getActivity(ActivityInstrumentationTestCase2.java:104)
at net.humblegames.bodylasticscalculator.test.MenuNavigationTestExperiment.setUp(MenuNavigationTestExperiment.java:42)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:191)
at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:176)
at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:554)
at com.google.android.apps.common.testing.testrunner.GoogleInstrumentationTestRunner.onStart(GoogleInstrumentationTestRunner.java:167)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1701)



Update #1

I created a clean eclipse project and a clean Espresso test for that project. I was able to reproduce the same error during Espresso test run (but I'm still unsure if the cause of this error is the same in my real app):

Target app has 3 activities (main, second, third). User navigates between them by clicking menu items: main activity starts the second, the second activity starts the third.

I found that if I call clickMenuItem() (see below) AND Thread.sleep(500) in the first test this causes crash in setUp() in getActivity() call before the second test. If timeout in sleep is less than 0.5 sec there is no crash.

At the same time if call Thread.sleep(10000) and don't call clickMenuItem() there is no crash also. It will successfully sleep for 10 seconds in the first test and will also pass the second one.



Test code:

public class MainActivityTest extends
        ActivityInstrumentationTestCase2<MainActivity> {

    private String TAG = getClass().getSimpleName();
    private Activity activity;

    public MainActivityTest() {
        super(MainActivity.class);    }

    public void setUp() throws Exception {
        Log.d(TAG, "SETUP");
        activity = getActivity();

        super.setUp();       }

    public void tearDown() throws Exception {
        Log.d(TAG, "TEARDOWN");

        super.tearDown();    }


    public void testFirst() throws InterruptedException {
        Log.d(TAG, "testFirst");

        clickMenuItem("Start second activity");

        clickMenuItem("Start third activity");

        Thread.sleep(500);
    }

    public void testSecond() {
        Log.d(TAG, "testSecond");
        // this test will not run     }


    private void clickMenuItem(String menuItem) {
        Log.d(TAG, "clickMenuItem");

        openActionBarOverflowOrOptionsMenu(getInstrumentation()
                .getTargetContext());

        onView(withText(menuItem)).perform(click());      }    }



Target code:

Main Activity:

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);         }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;        }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle item selection
        switch (item.getItemId()) {
        case R.id.action_start_second_activity:
            Intent intent = new Intent(this, SecondActivity.class);
            startActivity(intent);
            return true;

        default:
            return super.onOptionsItemSelected(item);
        }       }        }

Second Activity:

public class SecondActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);       }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.second, menu);
        return true;        }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle item selection
        switch (item.getItemId()) {
        case R.id.action_start_third_activity:
            Intent intent = new Intent(this, ThirdActivity.class);
            startActivity(intent);
            return true;

        default:
            return super.onOptionsItemSelected(item);
        }       }   }

Third Activity:

public class ThirdActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_third);    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.third, menu);
        return true;    }
}

回答1:

I found a workaround. I added code that hits back button multiple times in the tearDown() method to close all previously opened activities. Unfortunately I didn't found another method how to close all opened activities in Espresso. Robotium has a very convenient method for that purpose solo.finishOpenedActivities().

public void tearDown() throws Exception {
    Log.d(TAG, "TEARDOWN");

    goBackN();

    super.tearDown();
}

private void goBackN() {
    final int N = 10; // how many times to hit back button
    try {
        for (int i = 0; i < N; i++)
            Espresso.pressBack();
    } catch (com.google.android.apps.common.testing.ui.espresso.NoActivityResumedException e) {
        Log.e(TAG, "Closed all activities", e);
    }
}


回答2:

pressBack solution does not worked for me but I found another:

@Override
protected void tearDown() throws Exception {
    closeAllActivities(getInstrumentation());
    super.tearDown();
}

public static void closeAllActivities(Instrumentation instrumentation) throws Exception {
    final int NUMBER_OF_RETRIES = 100;
    int i = 0;
    while (closeActivity(instrumentation)) {
        if (i++ > NUMBER_OF_RETRIES) {
            throw new AssertionError("Limit of retries excesses");
        }
        Thread.sleep(200);
    }
}

public static <X> X callOnMainSync(Instrumentation instrumentation, final Callable<X> callable) throws Exception {
    final AtomicReference<X> retAtomic = new AtomicReference<>();
    final AtomicReference<Throwable> exceptionAtomic = new AtomicReference<>();
    instrumentation.runOnMainSync(new Runnable() {
        @Override
        public void run() {
            try {
                retAtomic.set(callable.call());
            } catch (Throwable e) {
                exceptionAtomic.set(e);
            }
        }
    });
    final Throwable exception = exceptionAtomic.get();
    if (exception != null) {
        Throwables.propagateIfInstanceOf(exception, Exception.class);
        Throwables.propagate(exception);
    }
    return retAtomic.get();
}

public static Set<Activity> getActivitiesInStages(Stage... stages) {
    final Set<Activity> activities = Sets.newHashSet();
    final ActivityLifecycleMonitor instance = ActivityLifecycleMonitorRegistry.getInstance();
    for (Stage stage : stages) {
        final Collection<Activity> activitiesInStage = instance.getActivitiesInStage(stage);
        if (activitiesInStage != null) {
            activities.addAll(activitiesInStage);
        }
    }
    return activities;
}

private static boolean closeActivity(Instrumentation instrumentation) throws Exception {
    final Boolean activityClosed = callOnMainSync(instrumentation, new Callable<Boolean>() {
        @Override
        public Boolean call() throws Exception {
            final Set<Activity> activities = getActivitiesInStages(Stage.RESUMED,
                    Stage.STARTED, Stage.PAUSED, Stage.STOPPED, Stage.CREATED);
            activities.removeAll(getActivitiesInStages(Stage.DESTROYED));
            if (activities.size() > 0) {
                final Activity activity = activities.iterator().next();
                activity.finish();
                return true;
            } else {
                return false;
            }
        }
    });
    if (activityClosed) {
        instrumentation.waitForIdleSync();
    }
    return activityClosed;
}


回答3:

This seems to work for me to close all of the activities in the stack except the first one.

   @After
   @Override
   public void tearDown() throws Exception
   {
      Intent intent = new Intent(getActivity(), getActivity().getClass());
      intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // Removes other Activities from stack
      intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
      myActivity.startActivity(intent);
      super.tearDown();

   }


回答4:

Try the following:

@Before
public void setUp() throws Exception {
    super.setUp();
    Log.d(TAG, "SETUP");
    activity = getActivity();
}


回答5:

It's work for me:

androidTestCompile ('com.android.support.test.espresso:espresso-contrib:2.2'){
    exclude group: 'com.android.support', module: 'appcompat'
    exclude group: 'com.android.support', module: 'support-v4'
    exclude module: 'recyclerview-v7'

}