Ehy y'all! I have a problem with testing with the ...
# coroutines
t
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
Cann't you make
selectById
suspend? It would be much simpler then.
g
This code has really bad testability by default because you hide launch in selectById
t
That is the problem... unfortunately. the dao call is synchronous, so I have to move it off the main thread in some ways
j
you can use
withContext
g
just use
withContext(IO) {}
instead
j
suspend fun selectById(id: Int) = withContext(Dispatchers.IO) { delay(5000) dao.syncOperationWith(id) }
t
Let me try, thanks!
g
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
the cancel is handled in the
onCleared
method, so that it can be canceled based on Android's lifecycle... is that wrong?
j
Yes, and one of the main reason it is bad practice is the poor testability.
t
How should I handle it, so that it is better?
g
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
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
It’s not wrong in general, but not sure that this is correct approach in your case
t
I see your point, thanks for the explanation!
f
I agree with the
suspend
method approach but here the job must be saved in the ViewModel
g
this is fine in some cases, but you pay with problems with testing, it’s general problem not really related to coroutines
f
so the ViewModel can be seen as the top level object
g
As alternative solution to test this code you can pass custom dispatcher with Unconfined context
f
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
You can use Unconfined instead in coroutines, which is like default scheduler in Rx
f
yes, but in that example it works only without the delay at the beginning of the method
g
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:
Copy code
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
I'll try this solution out as well, thanks!
g
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
Yup, it looks like something like that