androidtestingandroid-espresso

How to set a value on a Slider and trigger onChange or OnSliderTouchListener during Espresso testing?


When testing with Espresso, setting a value on a Slider directly does not trigger the onChange or OnSliderTouchListener callbacks. I haven't found any official documentation or reliable information online on how to properly simulate user interaction with a Slider in tests.


Solution

  • If you're using OnSliderTouchListener, here’s a solution that works using reflection:

    fun setValueAndClick(value: Int): ViewAction {
        return object : ViewAction {
            override fun getDescription(): String? {
                return "Set value and trigger onStopTrackingTouch."
            }
    
            override fun getConstraints(): Matcher<View?>? {
                return isAssignableFrom(Slider::class.java)
            }
    
            override fun perform(uiController: UiController?, view: View) {
                val slider = view as Slider
                slider.value = value.toFloat()
    
                // Slider extends BaseSlider
                val baseSliderClass = slider.javaClass.superclass
    
                // BaseSlider contains "List<T> touchListeners"
                val touchListenersField: Field = baseSliderClass.getDeclaredField("touchListeners")
                touchListenersField.isAccessible = true
    
                val listeners = touchListenersField.get(slider) as? List<*>
                val listener = listeners?.find { it is Slider.OnSliderTouchListener }
    
                if (listener == null) throw Exception("${Slider.OnSliderTouchListener::class.simpleName} not found.")
    
                for (method in listener.javaClass.declaredMethods) {
                    if (method.name == "onStopTrackingTouch") {
                        method.isAccessible = true
                        method.invoke(listener, slider)
                        break
                    }
                }
            }
        }
    }
    

    Usage Example:
    onView(withId(R.id.set_number_slider)).perform(setValueAndClick(10))

    You can call onStartTrackingTouch simply by replacing "onStopTrackingTouch" with it. I couldn't come up with a better way; I searched for ready-made solutions online, but they involved questionable calculations with "magic" numbers for correcting deviations. At least the reflection will work reliably.