意式浓缩咖啡-匹配器和存根

问题描述

我的应用程序首选项中有发送反馈。它应该打开一个选择器和一个新的Intent,以发送包含提供的电子邮件地址,主题和文本模板的电子邮件。这部分仅可在模拟器和物理设备上使用:

prefButtonSendFeedback?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
            val emailIntent = Intent(Intent.ACTION_SENDTO)
            emailIntent.type = "text/plain"
            emailIntent.data = Uri.parse("mailto:")
            emailIntent.putExtra(Intent.EXTRA_EMAIL,arrayOf(getString(R.string.send_Feedback_email_address)))
            emailIntent.putExtra(Intent.EXTRA_SUBJECT,getString(R.string.send_Feedback_email_subject))
            emailIntent.putExtra(Intent.EXTRA_TEXT,getString(R.string.send_Feedback_email_content_template))
            context?.startActivity(Intent.createChooser(emailIntent,getString(R.string.send_Feedback_intent_name)))
            true
        }

对于这种情况,我也进行了Espresso测试:

    @Test fun clickOnFeedbackActionMenuItem_StartsFeedbackFragment() {
        launchFragmentInContainer<SettingsFragment>()

        Intents.init()

        intending(
            hasAction(Intent.ACTION_CHOOSER)
        ).respondWith(
            ActivityResult(Activity.RESULT_OK,null)
        )

        // Press on Feedback navigation item
        onView(withId(androidx.preference.R.id.recycler_view))
            .perform(
                actionOnItem<RecyclerView.ViewHolder>(
                    hasDescendant(withText(R.string.pref_send_Feedback)),click()
                )
            )

        val emailIntent = allOf(
            hasAction(Intent.ACTION_SENDTO),hasData(Uri.parse("mailto:")),hasExtra(Intent.EXTRA_EMAIL,arrayOf(withText(R.string.send_Feedback_email_address))),hasExtra(Intent.EXTRA_SUBJECT,withText(R.string.send_Feedback_email_subject)),hasExtra(Intent.EXTRA_TEXT,withText(R.string.send_Feedback_email_content_template))
        )

        val expectedIntent = allOf(
            hasAction(Intent.ACTION_CHOOSER),hasExtra(equalTo(Intent.EXTRA_INTENT),emailIntent),hasExtra(equalTo(Intent.EXTRA_TITLE),withText(R.string.send_Feedback_intent_name))
        )

        intended(expectedIntent)

        Intents.release()
    }

我的问题通常与How to stub Intent.createChooser Intent using Espresso的问题相似

  • 使用类似代码中的匹配器时,我会出错:
androidx.test.espresso.base.DefaultFailureHandler$AssertionFailedWithCauseError: Wanted to match 1 intents. Actually matched 0 intents.

IntentMatcher: (has action: is "android.intent.action.CHOOSER" and has extras: has bundle with: key: "android.intent.extra.INTENT" value: (has action: is "android.intent.action.SENDTO" and has data: is <mailto:> and has extras: has bundle with: key: is "android.intent.extra.EMAIL" value: is [<with string from resource id: <3134850762>>] and has extras: has bundle with: key: is "android.intent.extra.SUBJECT" value: is <with string from resource id: <2131820702>> and has extras: has bundle with: key: is "android.intent.extra.TEXT" value: is <with string from resource id: <2131820701>>))

Matched intents:[]

Recorded intents:
-Intent { act=android.intent.action.CHOOSER (has extras) } handling packages:[[android]],extras:[Bundle[{android.intent.extra.INTENT=Intent { act=android.intent.action.SENDTO dat=mailto: (has extras) },android.intent.extra.TITLE=Send Feedback:}]])

  • 当我只有几个Matchers时,测试呈绿色-添加任何其他失败测试:
        val emailIntent = allOf(
            hasAction(Intent.ACTION_SENDTO),)

        val expectedIntent = allOf(
            hasAction(Intent.ACTION_CHOOSER),)
  • 我还使用了真断言和调试器
        val receivedIntent: Intent = Iterables.getonlyElement(Intents.getIntents())
        assertthat(receivedIntent).hasAction(Intent.ACTION_CHOOSER) ### here is a breakpoint

它告诉我,我的意图具有我需要的所有其他内容主题,地址和文字)-我删除了不必要的值:

receivedIntent =
  (...)
  mAction = "android.intent.action.CHOOSER"
  (..)
  mExtras = 
    mMap = 
      value[0] = 
        mAction = "android.intent.action.SENDTO"
        mData = "mailto:"
        mExtras = 
          mMap =
            value[0] = "App Feedback"
            value[1] = "Add here any bugs,issues or ideas about App"
            value[2] =
              0 = "[email protected]"
      value[1] = "Send Feedback:"

我的问题是,我的Matchers有什么问题-对我来说,他们看起来还不错:

此外,我不确定intending()的工作方式-我假设像在代码中那样使用它,在测试期间不会运行真正的Intent。因此它将不会运行电子邮件客户端。我错了-它可以运行,但是如果没有运行,即使我正在使用Intents.release(),所有下一个测试也会挂起。

解决方法

因此,一旦我对网络资源进行了更深入的研究,我发现了更复杂的Espresso匹配器:

        val expectedIntent = allOf(
            hasAction(Intent.ACTION_CHOOSER),hasExtra(
                equalTo(Intent.EXTRA_INTENT),allOf(
                    hasAction(Intent.ACTION_SENDTO),hasData(Uri.parse("mailto:")),hasExtra(
                        `is`(Intent.EXTRA_TEXT),`is`("Add here any bugs,issues or ideas about App")
                    ),hasExtra(
                        `is`(Intent.EXTRA_EMAIL),`is`(arrayOf("[email protected]"))
                    ),hasExtra(
                        `is`(Intent.EXTRA_SUBJECT),`is`("App Feedback")
                    )
                )
            ),hasExtra(
                `is`(Intent.EXTRA_TITLE),`is`("Send Feedback:")
            )
        )

        intended(expectedIntent)

很简单。

我仍然不了解其中的区别,所以如果有人可以向我解释一下...

我不知道,为什么这个可行:

            hasExtra(
                `is`(Intent.EXTRA_TITLE),`is`("Send Feedback:")
            )

而这个不是:

            hasExtra(
                `is`(Intent.EXTRA_TITLE),withText(R.string.send_feedback_intent_name)
            )

,

hasExtra(key: String,value: T)
-> hasEntry(is(key),is(value))

hasExtra(keyMatcher,valueMatcher)
-> BundleMatcher(keyMatcher,valueMatcher)

hasExtras(matcher: Mathcer)
-> 新的 TypeSafeMatcher
-> matcher.matches(intent.getExtras())

我就是这么用的。

intended(FilterMatcher.filter<Intent> { intent ->
    //check with intent  
})

class FilterMatcher<T> (klass: Class<T>,val filter: (data: T) -> Boolean): TypeSafeMatcher<T>(klass) {
    companion object {
        inline fun <reified T> filter(noinline filter: (data: T) -> Boolean): Matcher<T> = FilterMatcher(T::class.java,filter) as Matcher<T>
        inline fun <reified T: View> filterView(noinline filter: (data: T) -> Boolean): Matcher<View> = FilterMatcher(T::class.java,filter) as Matcher<View>
    }

    override fun describeTo(description: Description?) {
        description?.appendText("with filter");
    }

    override fun matchesSafely(item: T): Boolean = filter(item)
}
,

我仍然不明白其中的区别,所以如果有人可以向我解释...

withText 是用于 matching TextViewsMatcher<View> - 它专门寻找具有指定字符串资源 ID 的 TextView。除非您将具有相同资源 ID 的 TextView 推送到您的附加内容中,否则它不会起作用。