Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android Espresso Intents test randomly fail with ``init() must be called prior to using this method``

Tags:

I am working on pushing a project into espresso testing currently. I have read a bunch of documents and follow the given practises to get started.

Everything works fine, However, when it comes to Intents related test, the result is strange.

Most of the time, the tests passed in my Mac but fail in my colleague's Windows(not all tests fail) with the the fail message java.lang.IllegalStateException: init() must be called prior to using this method.

Quite strangely, If we Run Debug test in Android Studio flow the code step by step, it passes.

here is the test code:

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

  @Rule public IntentsTestRule<MainActivity> mRule = new IntentsTestRule<>(MainActivity.class, true, false);

  AccountManager accountManager;
  MainActivity   activity;


  private void buildLoginStatus() throws AuthenticatorException {
    DanteApp app = (DanteApp) InstrumentationRegistry.getTargetContext().getApplicationContext();
    accountManager = app.getDanteAppComponent().accountManager();

    DoctorModel doctorModel = AccountMocker.mockDoctorModel();
    accountManager.save(doctorModel.doctor);
    accountManager.setAccessToken(doctorModel.access_token, false);
  }

  @Before public void before() throws Exception {
    buildLoginStatus();

    // must login
    assertThat(accountManager.hasAuthenticated(), is(true));

    activity = mRule.launchActivity(null);
    // block all of the outer intents
    intending(not(isInternal())).respondWith(new Instrumentation.ActivityResult(Activity.RESULT_OK, null));
  }

  @After public void tearDown() throws Exception {
    accountManager.delete();
  }

  // failed
  @Test public void testViewDisplay() throws Exception {

    // check tabhost is displayed
    onView(withClassName(equalTo(TabHost.class.getName()))).check(matches(isDisplayed()));

    // check toolbar is displayed
    onView(withClassName(equalTo(ToolBar.class.getName()))).check(matches(isDisplayed()));
  }

  // passed
  @Test public void testCallServiceHotline() throws Exception {
    // switch to the account tab layout
    onView(withChild(withText(R.string.account))).perform(click());
    // click account menu to make a service call
    onView(withId(R.id.contact)).perform(click());

    // check call start expectly
    intended(allOf(
        not(isInternal()),
        hasAction(Intent.ACTION_DIAL),
        hasData(Uri.parse("tel:" + activity.getString(R.string.call_service)))
    ));
  }


  // failed
  @Test public void testOpenSettingsUI() throws Exception {
    // stub all internal intents
    Intents.intending(isInternal())
        .respondWith(new Instrumentation.ActivityResult(Activity.RESULT_OK, null));

    onView(withChild(withText(R.string.account))).perform(click());
    onView(withId(R.id.setting)).perform(click());

    // check open settings activity successfully
    intended(anyOf(
        hasComponent(SettingActivity.class.getName())
    ));
  }
}

The testing library version(nearly all dependencies are up to date and we use both physics devices and emulator to test):

  • rule: 0.4.1
  • runner: 0.4.1
  • espresso-*: 2.2.1
  • support-*: 23.1.0

Any idea deserves an appreciation. Thanks!

like image 635
longkai Avatar asked Nov 10 '15 07:11

longkai


2 Answers

Two Solutions:

  1. Use ActivityTestRule instead of IntentsTestRule and then in your @Before and @After manually call Intents.init() and Intents.release() respectively.
  2. Write a custom IntentTestRule and override beforeActivityLaunched() to include your AccountManager logic. Use afterActivityFinished for your current @After logic. This will also allow you to just use the default IntentTestRule constructor. (Preferred Solution)

As to why this is happening:

"Finally on an unrelated note, be careful when using the new IntentsTestRule. It does not initialize, Intents.init(), until after the activity is launched (afterActivityLaunched())." - Shameless plug to my own post (halfway down helpful visual)

I think you are running into a race condition where in your @Before method you are executing launchActivity() then espresso tries to execute intending(not(isInternal())).respondWith(new Instrumentation.ActivityResult(Activity.RESULT_OK, null)); before your activity is actually created, which means afterActivityLaunched() isn't called, which means neither is Intents.init(), crash!

Hope this helps.

like image 115
JabKnowsNothing Avatar answered Sep 21 '22 08:09

JabKnowsNothing


IntentsTestRule is derived from ActivityTestRule and should manage Intents.init() and Intents.release() for you.

However, in my case the IntentsTestRule did not work properly. So I switch back to ActivityTestRule and call Intents.init() before and Intents.release() after the test which sent the Intent.

For more information please see this reference.

like image 45
Petterson Avatar answered Sep 23 '22 08:09

Petterson