I'm struggling to make UI tests run reliable, I have a MVVM setup with a fragment with a ViewModel which has a UseCase. Depending on the result of the UseCase I show one of four views to the user: loading, error, no results and result view. The important part of the fragment layout:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_postbox"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/light_grey_06_dark_black"
app:visibleOrGone="@{postboxViewModel.viewData.hasData}" />
<include
android:id="@+id/includedNoResults"
layout="@layout/view_no_results_postbox"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:visibleOrGone="@{postboxViewModel.viewData.hasNoResults}" />
<include
android:id="@+id/includedError"
layout="@layout/view_error_postbox"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:viewModel="@{postboxViewModel}"
app:visibleOrGone="@{postboxViewModel.viewData.hasError}" />
<include
android:id="@+id/includedLoading"
layout="@layout/view_loading"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:visibleOrGone="@{postboxViewModel.viewData.isLoading}" />
</FrameLayout>
In my test class I don't call an API, I return only local mocked data from the UseCase. Test class:
@RunWith(AndroidJUnit4::class)
@LargeTest
class PostBoxFragmentTest : KoinTest {
@MockK(relaxed = true)
lateinit var loadPostboxUseCase: LoadPostboxUseCase
@Before
fun setup() {
MockKAnnotations.init(this)
if (GlobalContext().getOrNull() != null) {
stopKoin()
}
startKoin {
androidContext(InstrumentationRegistry.getInstrumentation().targetContext.applicationContext)
val testKoinViewModule = module {
viewModel {
PostboxViewModel(
application = get(),
mapper = PostboxItemListMapper(PostboxItemMapper(get())),
loadPostboxUseCase = loadPostboxUseCase
)
}
}
modules(listOf(testKoinViewModule))
}
}
@After
fun tearDown() {
unmockkAll()
stopKoin()
}
@Test
fun showLoadingViewTest() {
// Given
coEvery {
loadPostboxUseCase.load()
} returns flowOf(Resource.loading())
// When
launchFragmentInContainer<PostboxFragment>(themeResId = R.style.AppTheme)
// Then
onView(withId(R.id.includedLoading)).check(
matches(isDisplayed())
)
}
@Test
fun showNoResultsViewTest() {
// Given
coEvery {
loadPostboxUseCase.load()
} returns flowOf(Resource.success(emptyPostBoxDummyData))
// When
launchFragmentInContainer<PostboxFragment>(themeResId = R.style.AppTheme)
// Then
onView(withId(R.id.includedNoResults)).check(
matches(isDisplayed())
)
}
//2 more tests for results and error
Problem is now, when I run all 4 tests together randomly one or two of them are failing with:
androidx.test.espresso.base.DefaultFailureHandler$AssertionFailedWithCauseError: 'is displayed on the screen to the user' doesn't match the selected view. Expected: is displayed on the screen to the user Got: "ConstraintLayout{id=2131362084, res-name=includedLoading, visibility=GONE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, layout-params=android.widget.FrameLayout$LayoutParams@b329d0, tag=null, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=1}"
If I run the tests separately it works - even the failing ones. My guess, the emulator is too fast (animations are disabled) or the fragment is too slow. I tried it with an IdlingResource like it is described here Instrumental Test: Better Espresso without sleep but it didn't help, I got the same behavior. Since the UI tests are mandatory for the CI pipeline it would be nice, if all of them would run reliable. :)
If you need any more code or information please let me know and many thanks in advance!