My Activity has an EditText and a Button. When the button is pressed a long running function is called. During this time the EditText should be disabled. When the function has finished the EditText should be reenabled. This works fine when running the application however I have written an Espresso Unit Test to test this behaviour which does not seem to behave correctly.
It appears the long running function pauses the unit test which takes over 3 seconds to run. Once the long running function has finished, the unit test then tests if the EditText is disabled which it no longer is as the task has finished and the loading
variable is set back to false
I would expect the unit test to start the function then, as it is ran in a coroutine, it would continue to the next line to check the EditText is disabled.
I have tried all different variations of CommonPool, UI, launch, async, Deferred etc but nothing seems to get the correct behaviour.
suspend fun getData(): String {
// simulate network request delay
delay(3000)
return "Hello, world!"
}
fun onButtonClicked() {
// data binding field to disable EditText
loading = true
launch(CommonPool) {
// make "network call"
val data = getData().await()
// reenable EditText
loading = false
}
}
@Test
fun disableEditText() {
// check the EditText starts off enabled
onView(withId(R.id.edit_text))
.check(matches(isEnabled()))
// click the Button to simulate the network call
onView(withId(R.id.button))
.perform(click())
// check the EditText is disabled
onView(withId(R.id.edit_text))
.check(matches(not(isEnabled()))
}
Generally speaking, you should not handle any logic inside your view (activity, fragment, etc..) and it should be done in a separate logic handler (like ViewModel, Presenter or ..).
You can spy
your activity using a framework (like Mockito, or MockK), and mock the getData()
method to always return fast, this way your test-case does not need to wait for it.
To spy your activity using mockito, you can use the information from this answer, and use when(activity.getData()).thenReturn("")
to mock the method. Since you are mocking a coroutine, you need to use runBlocking
to run your test.
class MainActivityTest {
internal var subject: MainActivity
val activityFactory: SingleActivityFactory<MainActivity> =
object : SingleActivityFactory<MainActivity>(MainActivity::class.java) {
protected fun create(intent: Intent): MainActivity {
subject = spy(getActivityClassToIntercept())
return subject
}
}
@Rule
var testRule: ActivityTestRule<MainActivity> = ActivityTestRule(activityFactory, true, true)
@Test
fun sampleTest() = runBlocking<Unit> {
`when`(subject.getData()).thenReturn("")
//verify(subject).
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With