Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When using kotlin coroutines, how do I unit test a function that calls a suspend function?

I have a class like this

class SomeClass {     fun someFun() {         // ... Some synchronous code         async {             suspendfun()          }     }      private suspend fun suspendFun() {          dependency.otherFun().await()          // ... other code     } } 

I want to unit test someFun() so I wrote a unit test that looks like this:

@Test fun testSomeFun() {     runBlocking {         someClass.someFun()     }      // ... verifies & asserts } 

But this doesn't seem to work because runBlocking doesn't actually block execution until everything inside runBlocking is done. If I test suspendFun() directly inside runBlocking it works as expected but I want to be able to test someFun() all together.

Any clue how to test a function with both sync and async code?

like image 799
user1809913 Avatar asked Jan 05 '18 18:01

user1809913


People also ask

How does Kotlin handle suspend function?

We just have to use the suspend keyword. Note: Suspend functions are only allowed to be called from a coroutine or another suspend function. You can see that the async function which includes the keyword suspend. So, in order to use that, we need to make our function suspend too.

How does suspension work in Kotlin coroutines?

Suspending functions are at the center of everything coroutines. A suspending function is simply a function that can be paused and resumed at a later time. They can execute a long running operation and wait for it to complete without blocking.

Is async a suspend function Kotlin?

Note that these xxxAsync functions are not suspending functions. They can be used from anywhere. However, their use always implies asynchronous (here meaning concurrent) execution of their action with the invoking code. // but waiting for a result must involve either suspending or blocking.


1 Answers

Fixing async

As implemented, your someFun() will just “fire and forget” the async result. As a result, runBlocking does not make a difference in that test.

If possible, make someFun() return async's Deferred and then, in runBlocking, call await on it.

fun someFun(): Deferred<Unit> {     // ... Some synchronous code     return async {         suspendFun()     } } 

Then the test:

runBlocking {     SomeClass().someFun().await() } 

This question/answer is a good resource for further information.

alternative: use launch

It's also possible to avoid async in favor of using suspend functions and a launch-created coroutine:

suspend fun someFun() {     // ... Some synchronous code     suspendFun() }  private suspend fun suspendFun() {     delay(1000)     println("executed")     // ... other code } 

The test uses launch and the outer runBlocking implicitly waits for its completion:

val myScope = GlobalScope runBlocking {     myScope.launch {         SomeClass().someFun()     } } 
like image 52
s1m0nw1 Avatar answered Sep 19 '22 09:09

s1m0nw1