https://kotlinlang.org logo
#coroutines
Title
# coroutines
g

gergo

02/01/2019, 5:39 PM
👋 hello guys, im a newbie with coroutines. Im trying to test my ViewModel which simplified looks like the attached code. My issue is that sometimes the tests succeeds but sometimes fails. I can't figure out what am I doing wrong. Could someone help me with that? (the repeat is there to see if it works "all the time")
w

withoutclass

02/01/2019, 5:50 PM
your ViewModel has it’s own coroutine scope, so your
launch
is running in that scope instead of the
runBlocking
scope in your test. I also don’t see any code where you’re providing the coroutineContext in your ViewModel in order to satisfy CoroutineScope
👍 1
g

ghedeon

02/01/2019, 5:51 PM
You have to replace your Main dispatcher in tests, in case that's where you launch.
g

gergo

02/01/2019, 5:58 PM
I create the viewModel inside the test with EmptyCoroutineContext.
override val coroutineContext: CoroutineContext
this line provides the coroutineContext for the Scope implementation. So should I inject the runBlocking's CoroutineScope into the ViewModel then?
g

ghedeon

02/01/2019, 6:04 PM
I don't think so. Not sure what @withoutclass means, but ViewModel is supposed to have it's own scope. You don't have to pass anything.
I just can't tell about combining empty scope and context like this, never tried it.
w

withoutclass

02/01/2019, 6:05 PM
Typically you have to implement a getter for the coroutine context, though looking at it, providing that value in the constructor probably covers it
s

streetsofboston

02/01/2019, 6:16 PM
@gergo Try this:
Copy code
runBlocking {
        viewModel.start().join()
    }
   ...
    fun start() : Job {
        return launch{
            foo.value = "alma"
        }
    }
👍 1
The
start()
launches a Job and returns immediately. On occasion, if you try often enough, a call to
foo.value = "alma"
could slip through and gets executed, but sometimes, it may not.
g

gergo

02/01/2019, 6:22 PM
Thank you guys for the fast response. I see the issue with the launch returning almost immediately. You are right with join, it works as expected, but I still feel weird about changing the public api of the class to be able to test this. Isn't there some way I could inject something instead into the ViewModel to achieve similar result?
w

withoutclass

02/01/2019, 6:27 PM
You could have the launch be an
async
, and use a getter off the viewmodel wait for the value to return, or have the viewmodel return a deferred and let the calling code wait for the value. You could also use a channel for communicating values from the ViewModel, then the calling code will wait for the value to be emitted
s

streetsofboston

02/01/2019, 6:41 PM
@gergo In this case, you could use a
TestCoroutineContext
. Provide an instance of this to your ViewModel and call
triggerActions
on it.
Copy code
val testCoroutineContext= TestCoroutineContext()
    val viewModel = ViewModel(testCoroutineContext)
    viewModel.start()
    testCoroutineContext.triggerActions()
    assertEquals("alma", viewModel.foo.value)
(no
repeat
, no
runBlocking
in your test)
🎉 1
g

gergo

02/01/2019, 6:45 PM
Well, that's the closest to what I wanted to achieve, since I don't have to change the method signatures. Thank you @streetsofboston, @withoutclass and @ghedeon for the clarifications and the solutions!
g

ghedeon

02/01/2019, 6:59 PM
👍 1
🆒 1
2 Views