https://kotlinlang.org logo
#android
Title
# android
l

Lucas Kivi

09/13/2023, 10:23 PM
This PR made some changes to
runTest
in coroutines
1.7.0
that are causing us some problems.. (see thread)
🆘 1
We were previously doing the correct thing by using the
Thread
’s uncaught exception handler for exceptions thrown in other `CoroutineScope`s like so:
Copy code
fun <T : Throwable> assertThrowsInCoroutine(
    expectedThrowable: Class<T>,
    block: () -> Unit,
): T {
    val originalHandler = Thread.getDefaultUncaughtExceptionHandler()
    var throwable: Throwable? = null
    Thread.setDefaultUncaughtExceptionHandler { _, t -> throwable = t }

    try {
        block()
    } catch (t: Throwable) {
        // Manually catch this here just in case the exception is not in a coroutine
        throwable = t
    }

    Thread.setDefaultUncaughtExceptionHandler(originalHandler)

    return assertThrows(expectedThrowable) {
        throwable?.let { throw it }
    }
}
Copy code
private fun somethingThatBuildsACoroutineScopeAndThrows() {
    CoroutineScope(Dispatchers.Unconfined).launch {
        throw IllegalStateException("Something is happening")
    }
}

@Test
fun `original test`() {
    assertThrowsInCoroutine(IllegalStateException::class.java) {
        somethingThatBuildsACoroutineScopeAndThrows()
    }
}
However, as of
1.7.0
, this is broken. Now the exception goes uncaught and we see
org.opentest4j.AssertionFailedError: Expected java.lang.IllegalStateException to be thrown, but nothing was thrown.
However, if we wrap our
somethingThatBuildsACoroutineScopeAndThrows()
in
runTest
it succeeds!
Copy code
@Test
fun `new test`() {
    assertThrowsInCoroutine(IllegalStateException::class.java) {
        runTest {
            somethingThatBuildsACoroutineScopeAndThrows()
        }
    }
}
Does anyone know of a way to intercept this uncaught exception such that
assertThrowsInCoroutine
does not need to contain
runTest
?