'Is there an Espresso check for the state of a BottomSheetBehavior?

Is there a way in Espresso to check the state of a BottomSheetBehavior? For example, I would like to be able to write the following code to check that the BottomSheetBehavior attached to myLayout has state BottomSheetBehavior.STATE_COLLAPSED:

onView(withId(R.id.myLayout)).check(matches(hasBottomSheetBehaviorState(BottomSheetBehavior.STATE_COLLAPSED)))

Are there any Espresso matchers for a Layout's BottomSheetBehavior?



Solution 1:[1]

Adding onto Michael's answer, one thing that might help with a more friendly error message if your test fails is a simple function converting the Int into a user friendly message.

private fun getFriendlyBottomSheetBehaviorStateDescription(state: Int): String = when (state) {
    BottomSheetBehavior.STATE_DRAGGING -> "dragging"
    BottomSheetBehavior.STATE_SETTLING -> "settling"
    BottomSheetBehavior.STATE_EXPANDED -> "expanded"
    BottomSheetBehavior.STATE_COLLAPSED -> "collapsed"
    BottomSheetBehavior.STATE_HIDDEN -> "hidden"
    BottomSheetBehavior.STATE_HALF_EXPANDED -> "half-expanded"
    else -> "unknown but the value was $state"
}

fun hasBottomSheetBehaviorState(expectedState: Int): Matcher<in View>? {
    return object : BoundedMatcher<View, View>(View::class.java) {
        override fun describeTo(description: Description) {
            description.appendText("has BottomSheetBehavior state: ${getFriendlyBottomSheetBehaviorStateDescription(expectedState)}")
        }

        override fun matchesSafely(view: View): Boolean {
            val bottomSheetBehavior = BottomSheetBehavior.from(view)
            return expectedState == bottomSheetBehavior.state
        }
    }
}

Then if you were to run the test with the original sample.

onView(withId(R.id.myLayout)).check(matches(hasBottomSheetBehaviorState(BottomSheetBehavior.STATE_COLLAPSED)))

It would output the following instead of just a number.

Caused by: junit.framework.AssertionFailedError: 'has BottomSheetBehavior state: collapsed' doesn't match the selected view.

Also worth noting using the following approach does require creating an idling resource to avoid espresso trying to achieve this assertion while the bottom sheet behavior is not settled. There might be a better way, but I made use of the CountingIdlingResource to increment when the bottom sheet was settling and decrementing for everything else which worked in my use case of testing.

bottomSheetBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
       override fun onStateChanged(bottomSheet: View, newState: Int) {
            if (newState == BottomSheetBehavior.STATE_SETTLING) {
                countingIdlingResource.increment()
            } else {
                countingIdlingResource.decrement()
            }
        }

        override fun onSlide(bottomSheet: View, slideOffset: Float) {
        }
})

Solution 2:[2]

I couldn't find an existing Matcher but was able to write one which seems to work for this case. Here is hasBottomSheetBehaviorState in Kotlin:

fun hasBottomSheetBehaviorState(expectedState: Int): Matcher<in View>? {
    return object : BoundedMatcher<View, View>(View::class.java) {
        override fun describeTo(description: Description) {
            description.appendText("has BottomSheetBehavior state $expectedState")
        }

        override fun matchesSafely(view: View): Boolean {
            val bottomSheetBehavior = BottomSheetBehavior.from(view)
            return expectedState == bottomSheetBehavior.state
        }
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Andrew Steinmetz
Solution 2 Michael Osofsky