https://kotlinlang.org logo
Title
t

tiwiz

10/10/2018, 10:04 AM
Ehy y'all! I have a problem with testing with the new Coroutines. The problem is that I have a very long sync operation and I want to test the
runBlocking
behaviour happens even with a possible delay. In the following gist (https://gist.github.com/tiwiz/b85c72fb77148e6cc12aa378b762e94b) there is the code (the
delay
is only to test if the test waits or not). So far, we couldn't have it to wait, so we must be doing something wrong... does anyone have a clue about it?
j

Jonathan

10/10/2018, 10:08 AM
Cann't you make
selectById
suspend? It would be much simpler then.
g

gildor

10/10/2018, 10:09 AM
This code has really bad testability by default because you hide launch in selectById
t

tiwiz

10/10/2018, 10:09 AM
That is the problem... unfortunately. the dao call is synchronous, so I have to move it off the main thread in some ways
j

Jonathan

10/10/2018, 10:10 AM
you can use
withContext
g

gildor

10/10/2018, 10:10 AM
just use
withContext(IO) {}
instead
j

Jonathan

10/10/2018, 10:10 AM
suspend fun selectById(id: Int) = withContext(Dispatchers.IO) { delay(5000) dao.syncOperationWith(id) }
t

tiwiz

10/10/2018, 10:10 AM
Let me try, thanks!
g

gildor

10/10/2018, 10:11 AM
it’s just really bad practice in general (not only in coroutines) to run some parallel code as side effect without any handlers to join/await/cancel
👍 1
t

tiwiz

10/10/2018, 10:12 AM
the cancel is handled in the
onCleared
method, so that it can be canceled based on Android's lifecycle... is that wrong?
j

Jonathan

10/10/2018, 10:12 AM
Yes, and one of the main reason it is bad practice is the poor testability.
t

tiwiz

10/10/2018, 10:12 AM
How should I handle it, so that it is better?
g

gildor

10/10/2018, 10:13 AM
use suspend functions everywhere when it’s possible
👍 1
and use launch/await on most top level of your appliction
testing suspend functions is much easier even if this function returns Unit
t

tiwiz

10/10/2018, 10:14 AM
OK, in this case using
IO + job
is wrong, then?
I should handle it externally, like dividing the Selector from the
ViewModel
?
And have then the
ViewModel
invoke the
Selector
and clear the job?
g

gildor

10/10/2018, 10:14 AM
It’s not wrong in general, but not sure that this is correct approach in your case
t

tiwiz

10/10/2018, 10:16 AM
I see your point, thanks for the explanation!
f

fabiocollini

10/10/2018, 10:16 AM
I agree with the
suspend
method approach but here the job must be saved in the ViewModel
g

gildor

10/10/2018, 10:16 AM
this is fine in some cases, but you pay with problems with testing, it’s general problem not really related to coroutines
f

fabiocollini

10/10/2018, 10:16 AM
so the ViewModel can be seen as the top level object
g

gildor

10/10/2018, 10:18 AM
As alternative solution to test this code you can pass custom dispatcher with Unconfined context
f

fabiocollini

10/10/2018, 10:19 AM
on rx this can be done using a
TrampolineScheduler
to wait the end of the computation, is there something similar on the coroutines?
the Unconfined context works only if there aren’t any nested async/launch call
g

gildor

10/10/2018, 10:20 AM
You can use Unconfined instead in coroutines, which is like default scheduler in Rx
f

fabiocollini

10/10/2018, 10:21 AM
yes, but in that example it works only without the delay at the beginning of the method
g

gildor

10/10/2018, 10:21 AM
the Unconfined context works only if there aren’t any nested async/launch call
it’s not true anymore, coroutine builders now use coroutineScope context by default
Actually just realised, that dispatcher is not required here, Fabio is right it will not help without suspend function, But there is another approach:
val selector = Selector(dao)
selector.selectById(1)
selector.coroutineContext[Job]!!.join()
I don’t want to recommend it as universal solution, but at least you can wait until coroutineScope is running one of child coroutines
actually this job from scope is exactly that handler that I talked about, just never though about usage of scope in such case
t

tiwiz

10/10/2018, 10:32 AM
I'll try this solution out as well, thanks!
g

gildor

10/10/2018, 10:34 AM
btw I see your comment that you have StackOverflowError, probably you have this issue (fixed in develop): https://github.com/Kotlin/kotlinx.coroutines/issues/692
t

tiwiz

10/10/2018, 10:54 AM
Yup, it looks like something like that