Hi everyone, I’m having a bit of trouble with `run...
# coroutines
m
Hi everyone, I’m having a bit of trouble with
runBlocking
and CoroutineScopes. I want to launch a coroutine in a scope with a combined
Job
for this coroutine to run until the job is cancelled. It works fine, but I would like to wrap it in a
runBlocking
(for unit tests purposes), but unfortunately the
runBlocking
doesn’t wait for the inner launch... Do you have any idea why ? Or how could I make it work somehow ?
s
Both your `runBlocking`s should return almost immediately, since they only do a
launch
, which should return immediately…. But the first one never continue and your test hangs (it never gets to line number 11).
I got this to work:
Copy code
class ExampleUnitTest {
    private val testCoroutineContext = TestCoroutineContext()
    private val scope : CoroutineScope = object: CoroutineScope {
        override val coroutineContext: CoroutineContext = Job() + testCoroutineContext
    }

    @Test
    fun testChannel() {
        val channel = Channel<Int>()

        runBlocking {
            scope.launch {
                for (i in channel) {
                    println("A: $i")
                }
            }
        }

        val job = Job()
        runBlocking {
            (scope + job).launch {
                for (i in channel) {
                    println("B: $i")
                }
            }
        }

        scope.launch {
            for (i in 0 .. 10) {
                channel.send(i)
                if (i == 5) job.cancel()
            }
        }

        testCoroutineContext.triggerActions()
    }
}
And with
TestCoroutineContext
, you can remove any `runBlocking`:
Copy code
class ExampleUnitTest : CoroutineScope {
    private val testCoroutineContext = TestCoroutineContext()
        override val coroutineContext: CoroutineContext = Job() + testCoroutineContext

    @Test
    fun testChannel() {
        val channel = Channel<Int>()

        launch {
            for (i in channel) {
                println("A: $i")
            }
        }

        val job = Job()
        (this + job).launch {
            for (i in channel) {
                println("B: $i")
            }
        }

        launch {
            for (i in 0..10) {
                channel.send(i)
                if (i == 5) job.cancel()
            }
        }

        testCoroutineContext.triggerActions()
    }
}
m
Hey Anton ! Thanks a lot for taking the time to answer ! Your examples work fine, but they do not solve the problem I had : I wanted the
runBlocking
blocks to interrupt the thread until the job was cancelled. This way, if the job is not cancelled, the unit test would run forever. But your examples helped me find a way to solve this issue : I now launch the channel consuming coroutine outside of
runBlocking
, capture the associated job, and wait for its completion inside the
runBlocking
block.
You would get something like this :
Copy code
val channel = Channel<Int>()

    val job = Job()
    val scope: CoroutineScope = object : CoroutineScope {
        override val coroutineContext: CoroutineContext = job + Dispatchers.Default
    }

    val consumingJob = scope.launch {
        for (i in channel) {
            println("$i")
        }
    }
    runBlocking {
        delay(1_000)
        job.cancel()
        consumingJob.join()
    }
Thanks again !
👍 1