You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android Espresso:如何封装自定义组件视图查找逻辑以复用代码?

Absolutely! Encapsulating this lookup logic is a smart move—it cuts down on repetitive code and makes your tests way easier to maintain, especially if your custom component's layout changes down the line. Here's how to do it properly:

1. Create a Reusable Helper Function

First, centralize the lookup logic in a helper class or object. This way, every test that needs to access the EditText in your custom component just calls this function instead of repeating the entire allOf chain.

If you want to keep using internal IDs but centralize them (so you only update once if IDs change), here's an example:

object CustomComponentEspressoHelpers {
    // If you ever change R.id.editText to something else, just update it here
    private val INTERNAL_EDIT_TEXT_ID = R.id.editText

    fun getEditTextInCustomComponent(componentId: Int): ViewInteraction {
        return onView(
            allOf(
                withId(INTERNAL_EDIT_TEXT_ID),
                isDescendantOfA(withId(componentId))
            )
        )
    }
}

Then in your tests, replace the long lookup line with:

val editText = CustomComponentEspressoHelpers.getEditTextInCustomComponent(R.id.mobileEdt)

2. Make It Resilient to Layout Changes

If you want to avoid relying on internal IDs entirely (so even if you rename R.id.editText or restructure the component's layout, your tests don't break), use attribute-based matchers instead. For example, if your custom component has exactly one AppCompatEditText, target it by type:

object CustomComponentEspressoHelpers {
    fun getEditTextInCustomComponent(componentId: Int): ViewInteraction {
        return onView(
            allOf(
                isAssignableFrom(AppCompatEditText::class.java),
                isDescendantOfA(withId(componentId))
            )
        )
    }
}

If your component has multiple EditTexts, add more specific matchers to narrow it down—like using withHint(R.string.your_hint) or withInputType(InputType.TYPE_CLASS_TEXT):

fun getEditTextInCustomComponent(componentId: Int): ViewInteraction {
    return onView(
        allOf(
            isAssignableFrom(AppCompatEditText::class.java),
            withHint(R.string.mobile_number_hint),
            isDescendantOfA(withId(componentId))
        )
    )
}

3. Bonus: If Your Custom Component Is a Subclass

If you've wrapped your layout in a custom View subclass (e.g., class MobileEditTextComponent : ConstraintLayout(context)), you can make the matcher even cleaner by targeting the component class directly instead of its ID:

fun getEditTextInCustomComponent(componentMatcher: Matcher<View>): ViewInteraction {
    return onView(
        allOf(
            isAssignableFrom(AppCompatEditText::class.java),
            isDescendantOfA(componentMatcher)
        )
    )
}

Use it in tests like this:

// Target by ID
val editText = getEditTextInCustomComponent(withId(R.id.mobileEdt))

// Or target by tag if you have multiple instances
val editText = getEditTextInCustomComponent(withTagValue(`is`(R.id.mobile_edit_tag)))

Why This Works

  • Less duplication: No more copying the same allOf chain across multiple tests.
  • Easier maintenance: If your component's layout changes (e.g., you rename internal IDs or add new views), you only update the helper function once, not every test.
  • Cleaner tests: Your test code focuses on what you're testing, not the nitty-gritty of view lookup.

内容的提问来源于stack exchange,提问作者Rasoul Miri

火山引擎 最新活动