Search code examples
androidandroid-espressoandroid-testingandroid-uiautomator

Android Espresso testing ProgressBar visibility GONE before assert


I'm trying to test a spinner that should display while loading the information from an API. The problem is that I can't assert the initial state VISIBLE because it disappear too fast when the results are emitted back thus always having a failing test

Expected: (view has effective visibility <VISIBLE> and view.getGlobalVisibleRect() to return non-empty rectangle)

Got: view.getVisibility() was <GONE>

The first attempt using ui-automator

@Test
    fun displayLoaderWhileFetchingPlaylistDetails() {
        IdlingRegistry.getInstance().unregister(idlingResource)
        uiObjectWithId(R.id.playlist_list).getChild(UiSelector().clickable(true).index(0)).click()
        val spinner = uiObjectWithId(R.id.playlist_details_loader)
        assertTrue(spinner.exists())
    }

Another variant for the test without ui-automator

@Test
    fun displayLoaderWhileFetchingPlaylistDetails2() {
        IdlingRegistry.getInstance().unregister(idlingResource)
        onView(
            allOf(
                withId(R.id.playlist_image),
                isDescendantOfA(withPositionInParent(R.id.playlist_list, 0))
            )
        )
            .perform(click())
        assertDisplayed(R.id.playlist_details_loader)
    }

ui-automator helper

fun uiObjectWithId(@IdRes id: Int): UiObject {
        val resourceId = getTargetContext().resources.getResourceName(id);
        val selector = UiSelector().resourceId(resourceId)
        return UiDevice.getInstance(getInstrumentation()).findObject(selector)
    }

Fragment

private fun observeLoaderState() {
        viewModel.playlistLoader.observe(this as LifecycleOwner) { playlistSpinner ->
            when (playlistSpinner) {
                true -> playlist_details_loader.visibility = View.VISIBLE
                else -> playlist_details_loader.visibility = View.GONE
            }
        }
    }

ViewModel

class PlaylistDetailViewModel(
    private val repository: PlaylistRepository
) : ViewModel() {

    val playlistLoader = MutableLiveData<Boolean>()

    fun getPlaylistDetails(playlistId: String) = liveData {
        playlistLoader.postValue(true)
        emitSource(
            repository.getPlaylistDetailsById(playlistId)
                .onEach { playlistLoader.postValue(false) }
                .asLiveData()
        )
    }
}

Thanks!


Solution

  • First you need to create a rule using ActivityScenarioRule.

    in example:

    @get:Rule
    val mActivityRule = ActivityScenarioRule(MainActivity::class.java)
    

    ActivityScenarioRule, which is a JUnit rule used for functional testing of Android activities. The purpose of this rule is to launch the specified activity (MainActivity in this case) before each test method runs and handle its lifecycle during the test.
    Then you can write the test code for the display as below:

    @Test
    fun displaysLoaderWhileFetchingThePlaylist() {
        mActivityRule.scenario.onActivity {
            assertDisplayed(R.id.pbLoader)
        }
    }
    

    The mActivityRule.scenario property returns an instance of ActivityScenario, which represents the current state of the activity under test. The ActivityScenario provides various methods to interact with the activity, such as launching, finishing, or monitoring the state of the activity.
    onActivity executes a specific action in the main thread of the current Activity.
    As a result, mActivityRule.scenario.onActivity is used to safely perform UI interactions in test code and ensure that these interactions are executed in the main thread.

    I hope this answer will solve your problem.