如何在 Espresso 中实际启动 activity 的情况下检查发送的预期意图?

How can I check the expected intent sent without actually launching activity in Espresso?

我有一个 UI 测试,它单击一个按钮,然后在其 onClickListener 中启动一个新的 Activity。该测试检查是否发送了预期的意图。

我的问题是,我想测试是否发送了预期的意图 而没有实际启动 activity。因为我发现 new activity 初始化了它的状态,它使后续测试不稳定。

我知道有两个Espresso Intents API, which are <a href="https://developer.android.com/reference/android/support/test/espresso/intent/Intents.html#intended(org.hamcrest.Matcher%3Candroid.content.Intent%3E)" rel="nofollow noreferrer">intended</a> and <a href="https://developer.android.com/reference/android/support/test/espresso/intent/Intents.html#intending(org.hamcrest.Matcher%3Candroid.content.Intent%3E)" rel="nofollow noreferrer">intending</a>,但是都不能满足我的需求。 intended API 实际上启动目标 activity,而 intending API 不启动 activity,但它调用 onActivityResult我也不想要的回调。因为我担心 onActivityResult 中的代码可能会导致另一个漏洞。

另外intending不断言是否发送了匹配的意图。它只是在找到匹配意图时调用 onActivityResult 回调,这意味着我必须检查是否调用了 onActivityResult

有没有一种干净的方法来实现我想要的?

Espresso 的 Intents class 简洁好用 api,但当它不能满足您的需求时,还有一个替代方案。如果你使用AndroidJUnit4测试运行器,你可以获得<a href="https://developer.android.com/reference/android/app/Instrumentation.html" rel="nofollow">Instrumentaion</a> instance using <a href="https://developer.android.com/reference/android/support/test/InstrumentationRegistry.html#getInstrumentation()" rel="nofollow">InstrumentationRegistry.getInstrumentation()</a>, and then you can add <a href="https://developer.android.com/reference/android/app/Instrumentation.ActivityMonitor.html" rel="nofollow">Instrumentation.ActivityMonitor</a>实例。

Instrumentation.ActivityMonitor am = new Instrumentation.ActivityMonitor("YOUR_ACTIVITY", null, true);
InstrumentationRegistry.getInstrumentation().addMonitor(am);
onView(withId(R.id.view_id_to_perform_clicking)).check(matches(isDisplayed())).perform(click());
assertTrue(InstrumentationRegistry.getInstrumentation().checkMonitorHit(am, 1));

ActivityMonitor 构造函数的第三个参数告诉我们要阻止 activity 启动。请注意,这种方法有其局限性。与 Espresso Intents 的 rich Matcher support 相比,您不能为 ActivityMonitor.

设置多个条件

您可以在 ApiDemos, especially in <a href="https://android.googlesource.com/platform/development/+/master/samples/ApiDemos/src/com/example/android/apis/app/ContactsSelectInstrumentation.java" rel="nofollow">ContactsSelectInstrumentation</a> class 中找到多个示例。

如果您想测试预期的意图是否在没有实际启动 activity 的情况下发送,您可以通过使用 activityResult 捕获意图然后捕获 activity 来实现:

Intent intent = new Intent();
ActivityResult intentResult = new ActivityResult(Activity.RESULT_OK,intent);

intending(anyIntent()).respondWith(intentResult);

onView(withId(R.id.view_id_to_perform_clicking)).check(matches(isDisplayed())).perform(click());

intended(allOf(hasComponent(ActivityToBeOpened.class.getName())));

这会捕获任何启动 ActivityToBeOpened 的尝试。如果您想更具体一些,您还可以使用 Extras 捕捉意图:

intended(allOf(hasComponent(ActivityToBeOpened.class.getName()), hasExtra("paramName", "value")));

希望对您有所帮助。

实际上,您可以阻止任何启动外部或您自己的 Intent activity,但仍然使用丰富的 Espresso Intents API:

    Instrumentation.ActivityMonitor soloMonitor = solo.getActivityMonitor();
    instrumentation.removeMonitor(soloMonitor);
    IntentFilter filter = null;
    // Block any intent
    Instrumentation.ActivityMonitor monitor = instrumentation.addMonitor(filter, null, true);
    instrumentation.addMonitor(soloMonitor);

    // User action that results in an external browser activity being launched.
    user.clickOnView(system.getView(R.id.callButton));
    instrumentation.waitForIdleSync();

    Intents.intended(Matchers.allOf(
            IntentMatchers.hasAction(Matchers.equalTo(Intent.ACTION_VIEW)),
            IntentMatchers.hasData(Matchers.equalTo(Uri.parse(url))),
            IntentMatchers.toPackage(chromePackage)));

    instrumentation.removeMonitor(monitor);

你能够做到这一点,因为 Espresso Intents 仍然记录每个 Intent IntentMonitor callback even if you block them. Look at the source code of Espresso Intents 他们是如何做到的。

如果您使用 Robotium Solo 框架,您需要将自己的 ActivityMonitor 移到他们的框架之前。否则就跳过与此相关的行。