Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android Instrumentation blocked in coroutine

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()))
}
like image 809
Dre Avatar asked Nov 08 '22 08:11

Dre


1 Answers

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).
    }

}
like image 165
Adib Faramarzi Avatar answered Nov 14 '22 21:11

Adib Faramarzi