i’m having trouble with verification of suspending...
# coroutines
m
i’m having trouble with verification of suspending functions with mockito failing randomly. the tests most often pass, but will occasionally fail and i’m not sure why. for example:
Copy code
@Test
    fun `Submits app selection if selections can be made`() {
        mainActivityViewModel.submitAppSelection(selectedApp)

        runBlocking {
            verify(mockAppsStartupFsm).submitEvent(AppSelected(selectedApp))
        }
    }
Copy code
class MainActivityViewModel {
    fun submitAppSelection(app: App) {
        if (!selectionsCanBeMade()) return
        lastSelectedApp = app

        val coroutineScope = CoroutineScope(Dispatchers.Default)
        coroutineScope.launch { appsStartupFsm.submitEvent(AppSelected(app)) }
    }
}
AppsStartupFsm#submitEvent
is a suspending function. Changing the runBlocking scope to be function level doesn’t seem to help. Is there something I’m missing with verification of suspending functions?
@groostav is the way you’re solving the issue of parallelism just by using the main dispatcher in both places? if so, that’s kind of out of the question, as that suspension function sometimes does work that cannot be handled on Android’s main thread
joining a job would maybe work, but is also kind pointless: the coroutines are meant to be fired and forgotten, but they affect state changes which are then observed
g
ah theres a lot to get to here lol
yeah so, Firstly you can use the code I wrote below to make it concurrent bug single-threaded. Then you need to find some way to synchronize on the
submitEvent
call to ensure its gone off. As the untaggable Allen Wang suggested, you could return a `Job?`:
Copy code
fun submitAppSelection(app: App): Job? {
  if (!selectionsCanBeMade()) return null

  return launch { app.submitEvent(AppSelected) }
}
then, in your test, call
mainActivity.submit()?.join()
m
okay, i can see how that would work. i don’t love the nullable but i can maybe rearrange logic to avoid it
would it be bad practice to just
delay
for some short amount in the test to ensure that the coroutine is launched?
g
very much so
m
alright, thanks for your help.
g
A less brittle (but still bad, and also kind've interesting) solution is
launch(CoroutineDispatchers.Unconfined)
m
how would that solve the issue?
g
unconfined
will cause the
launch
block to start immediatley, and only return back to the caller of
launch
after the first suspension point
is the way you’re solving the issue of parallelism just by using the main dispatcher in both places?
Exactly, through a great deal of effort I can honeslty say this is the best approach. Start off writing single-threaded concurrent code. Parallelize when you need the performance.
that’s kind of out of the question, as that suspension function sometimes does work that cannot be handled on Android’s main thread
this is really odd. This style of suspend-funs-may-return-fast is in C# but was intentionally made more difficult (ie removed) in kotlinx because of the subtle concurrency bugs it can introduce. Typically we statically know if you will suspend or not, the runtime does not get a say in that.
I wouldnt do any of this, I'll make a gist with what I owould do
but regarding the
submitEvent
function, is it blocking or is it suspending? Is that an android API call?
m
hmmm. you’re making me rethink my work a bit
g
hah, sorry! I'm trying to make this simple I promise.
m
submitEvent
is technically marked as a suspending function, but it never actually suspends anything. i guess i’ve been using the suspend modifier to also indicate functions that need to happen off the main thread
android will throw exceptions if things like network and database io are done on the main thread
g
returning a
Job?
is a good quick fix, theres a bunch of places in my code where I've had to do that, particularly when old
java
is involved. But I think you might be better off doing a
withContext
and just making
submitAppSelection
tagged as
suspend
m
that could work. i was just trying to have the suspension point start in that class to associate the logic of getting off the main thread at that layer of the architecture
g
m
cool, i can dig that approach
can you explain a little more what you mean by your comment `// 2. synchronize on the result, only after
submitEvent
returns (not suspends) will
submitAppSelection
return.`
g
so
withContext
never does anything in parallel, it will return only when the block inside it returns. Thus your function is made synchronous. It suspends, and it does not hold on to the
Main
thread because the thing that might block (the
submitEvent
) call is not on the MainThread, its on a worker thread.
alternatively put, if you had
Copy code
println("before withContext!")
withContext(Dispatchers.Anything){
  println("before submitEvent!")
  submitEvent()
  println("after submitEvent!")
}
println("after withContext!")
you would only ever see the exact sequence
Copy code
before withContext!
before submitEvent!
after submitEvent!
after withContext!
lastly,
submitEvent
is technically marked as a suspending function, but it never actually suspends anything. i guess i’ve been using the suspend modifier to also indicate functions that need to happen off the main thread
I think you should follow that
suspend
up with a
withContext
. This is exactly what I do with my database facade.
Copy code
class SuspendingTable {
  suspend fun get(id: UUID): T   {
    return withContext(<http://Dispatchers.IO|Dispatchers.IO> + Trace){
      backingDb.getBlocking(id)
    }
  }
}
m
so if i decorate (idk if that’s the right terminology) the suspending
submitEvent
function with
withContext(Dispatchers.Default)
i can also remove the
withContext
call from
submitAppSelection
, correct?