Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Coroutines Test exception is not handled with neither TestCoroutineDispatcher nor TestCoroutineScope

Using this manual to test Coroutines. Writing a test that expected to throw exception crashes instead of passing the test. I wonder what i'm doing wrong.

    private val testDispatcher = TestCoroutineDispatcher()

    @Before
    fun setup() {
        // provide the scope explicitly, in this example using a constructor parameter
        Dispatchers.setMain(testDispatcher)
    }

    @After
    fun cleanUp() {
        Dispatchers.resetMain()
        testDispatcher.cleanupTestCoroutines()
    }

    @Test(expected = RuntimeException::class)
    fun testSomeFunctionWithException() = testDispatcher.runBlockingTest {
        someFunctionWithException()
    }


    private fun someFunctionWithException() {
        MainScope().launch {
            throw RuntimeException("Failed via TEST exception")
        }
    }

The test method above and the one below

    private val testScope = TestCoroutineScope()
    private lateinit var subject: Subject

    @Before
    fun setup() {
        // provide the scope explicitly, in this example using a constructor parameter
        subject = Subject(testScope)
    }

    @After
    fun cleanUp() {
        testScope.cleanupTestCoroutines()
    }


    @Test(expected = RuntimeException::class)
    fun testFooWithException() = testScope.runBlockingTest {
        subject.fooWithException()
    }

    class Subject(private val scope: CoroutineScope) {


        fun fooWithException() {
            scope.launch {
                println("fooWithException() thread: ${Thread.currentThread().name}")
                throw RuntimeException("Failed via TEST exception")
            }
        }
    }

both of them crash even though

Note: Prefer to provide TestCoroutineScope when it does not complicate code since it will also elevate exceptions to test failures.

  1. Why both of them crash?
  2. Why the one with scope does not fail instead of crashing?
like image 629
Thracian Avatar asked May 11 '20 13:05

Thracian


2 Answers

TestCoroutineScope uses TestCoroutineExceptionHandler which will handle all the exceptions thrown in the coroutine collecting them in the uncaughtExceptions list, although the first one will be rethrown during cleanUp or more specifically when cleanupTestCoroutines() is called, so you have to do something about that exception to prevent failing the tests.

@After
fun cleanUp() {
    try {
        testScope.cleanupTestCoroutines()
    } catch (e: Exception) {
        //Do something here
    }
}

During the tests you can inspect the uncaughtExceptions list in order to make your assertions:

@Test(expected = RuntimeException::class)
fun testFooWithException() = testScope.runBlockingTest {
    subject.fooWithException()
    assertEquals(1, uncaughtExceptions.size)
    assertEquals(uncaughtExceptions[0].message, "Failed via TEST exception")
}
like image 85
Glenn Sandoval Avatar answered Sep 30 '22 11:09

Glenn Sandoval


I do not have an environment where I can easily test the following, but try put your runBlocking blocks inside the body of your test functions. For example:

@Test
fun myTest() {
    runBlocking {
        // Do your test stuff here
    }
}

I have found that I have issues in test cases were I use the fun myTest() = runBlocking{ declaration before. It seems that the test runner is not able to detect the execution of the test with out some sort of assertion in the body, and where there is not a return. Something like that.

Any way, hope that helps.

like image 39
Laurence Avatar answered Sep 30 '22 10:09

Laurence