java.lang.VerifyError Precise Reference expected while trying to runBlocking Kotlin suspended fun in Android

Viewed 2045

I'm trying to write a unit test waiting for completion of a kotlin suspended function before checking results like this :

@Test
fun shouldSetupThingsProperly() {
    val context = InstrumentationRegistry.getInstrumentation().context
    runBlocking { MyObject.enable(context, false) }
    Assert.assertTrue( /* whatever usefull */ true)
}

The suspending methods are as follow :

object MyObject {

    @JvmStatic
    suspend fun enable(context: Context, enable: Boolean) {
        withContext(Dispatchers.IO) {
            // ... do some work
            wakeup(context)
        }
    }

    private suspend fun wakeup(context: Context) {
        withContext(Dispatchers.IO) {
            try {
                // setup things ...
            } catch (ignore: Exception) {}
        }
    }

}

Test run ends with :

java.lang.VerifyError: Verifier rejected class MyObject: java.lang.Object MyObject.enable(android.content.Context, boolean, kotlin.coroutines.Continuation) failed to verify: java.lang.Object MyObject.enable(android.content.Context, boolean, kotlin.coroutines.Continuation): [0x16] register v7 has type Reference: android.content.Context but expected Precise Reference: MyObject (declaration of 'MyObject' appears in /data/app/test-_rphd0tDrOp0KM-Bz09NWA==/base.apk!classes2.dex)
at MyObject.enable(Unknown Source:0)

I'm not familiar with coroutine and I was wondering how to achieve waiting for completion of the enable suspended function inside the test properly or if error was due to some other mistake...

2 Answers

Testing coroutines is a trick, even after some experience. If you can import, this will be very helpful: https://github.com/Kotlin/kotlinx.coroutines/tree/master/kotlinx-coroutines-test

If you have this dependency testing coroutines becomes much more manageable.

First off, if you can have the dispatcher you are running this a variable or parameter that can be set or overridden it will help you increase your testability.

As far as writing the test you can do something like:

@Before
fun before() {
    Dispatchers.setMain(mainThreadSurrogate)
}

@Test
fun shouldSetupThingsProperly() = runBlockingTest {
    val context = InstrumentationRegistry.getInstrumentation().context
    MyObject.enable(context, false, Dispatchers.Main)
    Assert.assertTrue( /* whatever useful */ true)
}

Your object itself will have I would say more of the changes

object MyObject {

    @JvmStatic
    suspend fun enable(context: Context, enable: Boolean, dispatcher: CoroutineDispatcher = Dispatchers.IO) {
         // If you need a return feel free to use withContext such as:
         // val result = withContext(dispatcher) { /* Return Value */ Any() }

        CoroutineScope(dispatcher).run {
            // ... do some work
            wakeup(context)
        }
    }

    private suspend fun wakeup(context: Context) {
        // Another coroutine scope is unnecessary here, it will inherit the parent scope automatically, so you can call
        // async functions here
        delay(200)

        try {
            // setup things ...
        } catch (exc: Exception) {
            // We had an issue
        }
    }
}
Related