EspressoでNestedScrollViewをscrollToする方法


NestedScrollViewをscrollToするとこんなエラーが出ます。

Caused by: java.lang.RuntimeException: Action will not be performed because the target view does not match one or more of the following constraints:
(view has effective visibility=VISIBLE and is descendant of a: (is assignable from class: class android.widget.ScrollView or is assignable from class: class android.widget.HorizontalScrollView or is assignable from class: class android.widget.ListView))

これは内部でScrollView, HorizontalScrollView, ListViewでしか動作しないように明示的にチェックしているからです。
このScrollToActionクラスはfinalで定義されているので継承できません。
コピーしてNestedScrollToActionというクラスを定義することで問題を解決します。
kotlinでコーディングしているので自動変換されたkotlinコードをもとにしています。

NestedScrollToAction.kt
class NestedScrollToAction : ViewAction {

    override fun getConstraints(): Matcher<View> {
        return allOf<View>(
            withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE),
            isDescendantOfA(
                anyOf<View>(
                    isAssignableFrom(NestedScrollView::class.java), // ここを追加
                    isAssignableFrom(ScrollView::class.java),
                    isAssignableFrom(HorizontalScrollView::class.java),
                    isAssignableFrom(ListView::class.java)
                )
            )
        )
    }

    override fun perform(uiController: UiController, view: View) {
        if (isDisplayingAtLeast(90).matches(view)) {
            Log.i(TAG, "View is already displayed. Returning.")
            return
        }
        val rect = Rect()
        view.getDrawingRect(rect)
        if (!/* immediate */view.requestRectangleOnScreen(rect, true)) {
            Log.w(TAG, "Scrolling to view was requested, but none of the parents scrolled.")
        }
        uiController.loopMainThreadUntilIdle()
        if (!isDisplayingAtLeast(90).matches(view)) {
            throw PerformException.Builder()
                .withActionDescription(this.description)
                .withViewDescription(HumanReadables.describe(view))
                .withCause(
                    RuntimeException(
                        "Scrolling to view was attempted, but the view is not displayed"
                    )
                )
                .build()
        }
    }

    override fun getDescription(): String {
        return "scroll to"
    }

    companion object {
        private val TAG = NestedScrollToAction::class.java.simpleName
    }
}

nestedScrollToという名前でテストから使用できるようにアクセス用の関数も定義します。

NestedViewActions.kt
class NestedViewActions {
    companion object {
        fun nestedScrollTo(): ViewAction {
            return ViewActions.actionWithAssertions(NestedScrollToAction())
        }
    }
}

これでscrollToの代わりにnestedScrollToを使えばNestedScrollViewを使ったテストが可能になります。

参考