Daniele Segato
08/04/2021, 7:59 AMrunBlockingTest {
val mutex = Mutex(true)
val mockedApi = mock<MyApi> {
onBlocking { invoke() } doSuspendableAnswer {
mutex.withLock {
// wait unlock to run
}
}
}
val sut = MyTestedClass(
api = mockedApi,
)
println("async callA"}
val callA = async { sut.doCall() }
println("async callB"}
val callB = async { sut.doCall() }
mutex.unlock()
// ....
}
this test never completes, it runs forever and stop at async callA
the async callB
is NEVER printed
the MyTestedClass
class MyTestedClass(private val api: MyApi) {
fun doCall() { // this is NOT suspendable
runBlocking {
api() // but this is suspendable
}
}
}
Isn't async supposed to be non-blocking? I would expect callA and callB to be executed concurrently until they reach the mutex but apparently the first async is blocking in the test and stop at the mutex blocking all the test.
What am I missing here?mateusz.kwiecinski
08/04/2021, 8:20 AMrunBlocking
does not suspend but blocks the thread, which is never desired effect when using coroutines. (I totally recommend this article: https://elizarov.medium.com/blocking-threads-suspending-coroutines-d33e11bf4761) It is safe to say that you never want to use runBlocking
.
In your scenario you block the thread your test invokes on, hence the lack of completion. You can’t invoke i.e. println("async callB")
because the test thread thread that supposed to do so is waits until runBlocking
completes
Most probably you want to define your doCall
method as suspend fun
Daniele Segato
08/04/2021, 8:22 AMmateusz.kwiecinski
08/04/2021, 8:30 AMDaniele Segato
08/04/2021, 8:40 AMval threadPool = Executors.newFixedThreadPool(3)
val dispatcher = threadPool.asCoroutineDispatcher()
try {
// ...
val callA = async(dispatcher) { sut.doCall() }
val callB = async(dispatcher) { sut.doCall() }
mutext.unlock()
// ...
joinAll(callA, callB)
} finally {
dispatcer.close()
threadPool.shutdownNow()
}
the external runBlockingTest gives me
java.lang.IllegalStateException: This job has not completed yet
looks like it is not waiting for those jobs to complete in joinAllit is supposed to simulate the real deal.what I mean by this is that the use of coroutine is an implementation detail, the interceptor shouldn't know about it
runBlocking
?val threadPool = Executors.newFixedThreadPool(3)
val callA = threadPool.submit(Runnable { sut.doCall() })
val callB = threadPool.submit(Runnable { sut.doCall() })
callA.get()
callB.get()
try {
} finally {
threadPool.shutdownNow()
}
thanksmateusz.kwiecinski
08/04/2021, 9:02 AMDaniele Segato
08/04/2021, 9:14 AM