Currently i am implementation UI test/ Instrumentation Test with Espresso, to test FragmentMovie at loading state, and success state. Due to multiple test library and other stuff,my test Instrumentation must contains hilt ijection.
UI testing scenario :
- When loading state, Check my loading progressbar and text loading is displayed
- When succes state, hide loading, and show recycle view
- Done
Problem
Loading state is skiped when running test. @Test will run after ViewModel postValue Success state. So, I can't test while loading state.
when i not write onView(withId(R.id.progressBar)).check(matches(isDisplayed())) the test is passed, but i want to test while loading state.
Possible fix, add delay(10000) to my view model, that will extend duration of loading state, but, my test still waiting success state.
Currently i am not use IdlingResource, because that not fix my problem,
Actual scenario:
- Fragment Run,
- ViewModel fetchdata,
- ViewModel loading,
- ViewModel success,
- FragmentTest run,
- viewAssert failed,
Expected :
- Fragment Run,
- ViewModel fetchdata,
- ViewModel loading,
- FragmentTest check progressbar,
- ViewModel success,
- FragmentTest check recycleView,
- done
Why? and How?
Code
Observer from fragmentMovie
private val viewModel: MovieViewModel by viewModels()
[...]
viewModel.movie.observe(viewLifecycleOwner, {
Log.d(TAG, "viewModelgetMovie: ${it.status}")
when (it.status) {
Status.LOADING -> {
Toast.makeText(requireContext(), "loading..", Toast.LENGTH_SHORT).show()
}
Status.SUCCESS -> {
binding.progressBar.visibility = View.GONE
binding.txLoading.visibility = View.GONE
if (it.data != null) {
list.addAll(it.data)
movieAdapter.notifyDataSetChanged()
}
}
Status.ERROR -> {
binding.progressBar.visibility = View.VISIBLE
binding.txLoading.text = it.message
Toast.makeText(requireContext(), "${it.message}", Toast.LENGTH_SHORT).show()
}
}
})
viewmodel
@HiltViewModel
class MovieViewModel @Inject constructor(
private val repository: MovieRepository,
private val dispatcher: CoroutineDispatcher
) : ViewModel() {
// new code
private val moviePost = MutableLiveData<ResourceHelper<ArrayList<MovieData>>>()
val movie: LiveData<ResourceHelper<ArrayList<MovieData>>>
get() = moviePost
init {
fetchMovie()
}
private fun fetchMovie() {
viewModelScope.launch(dispatcher) {
moviePost.postValue(ResourceHelper.loading(null))
repository.getMovie(1).let { movie ->
moviePost.postValue(movie)
}
}
}
}
MovieFragmentTest
package com.unlink.moviecatalogue6.ui.movie
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.unlink.moviecatalogue6.MainActivity
import com.unlink.moviecatalogue6.R
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@HiltAndroidTest
@RunWith(AndroidJUnit4::class)
@LargeTest
class MovieFragmentTest {
@get:Rule(order = 0)
var hiltRule = HiltAndroidRule(this)
@get:Rule(order = 1)
var activityRule = ActivityScenarioRule(MainActivity::class.java)
@Before
fun setUp() {
hiltRule.inject()
}
@After
fun tearDown() {
}
@Test
fun happyPath() {
onView(withId(R.id.progressBar)).check(matches(isDisplayed())) // error
// how can i test on loading?
onView(withId(R.id.recycleMovie)).check(matches(isDisplayed()))
}
}
Error
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: "ProgressBar{id=2131231016, res-name=progressBar, visibility=GONE, width=1080, height=44, 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=androidx.constraintlayout.widget.ConstraintLayout$LayoutParams@a455ee2, tag=null, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0}"
The error shown because my progress bar has gone. Because my test check the progress bar after success state.
Screenshoot
Loading state, but my test not run @Test
Success state, then @Test start
that cause error view is gone
Any response will very appreciated