I’ve a `ViewModel` that has a `viewModelScope.laun...
# coroutines
t
I’ve a
ViewModel
that has a
viewModelScope.launch{}
on the
init{}
. I’m trying to run a test where I set
Dispatchers.setMain(StandardTestDispatcher())
but the
launch{}
is running before I call
advanceUntilIdle()
. Why is that? Can someone give me a light 😅
s
Are you doing the
setMain
early enough? Also, I know it might not be super helpful to you now, but could you move that out of the
init
in the first place 😅 Every time I’ve done that I realized just how brittle that entire thing is and have ended up re-writting it in a much better way instead.
t
Yeah, it's the first thing in the test init. I also don't like this but it's a big project and many places are doing it. It used to work in a very old version of coroutines but now that we are upgrading to the latest one it started failing. Still I'm not sure why it would launch automatically even after setting the Standard dispatcher
u
where do you construct the viewmodel? as part of the test or as part of annotated test setup?
s
Yeah that's a good question. Hence I asked if you set the main dispatcher early enough. It could be that your VM is initialized before that runs. Some logs would also be helpful here before/after setting main/init block
t
u
well, order looks right
t
still, if I debug the code, I can see the
state.update { "B" }
running automatically before the test runs 😕
s
Do you see the same behavior if you move the construction of the ViewModel in the test itself instead of in the @Before ?
t
@Stylianos Gakis then it works 😵 Why?
s
🪄
😭 1
Do you experience the same thing if you only got 1 test case vs when you have more and therefore have the @Before block run more than once?
t
If I repeat the code block many times all of them fail when it’s set on @ Before
s
@Before Old confused as hell right being tagged here 😂
😆 1
Right, but if it's only 1 test case, does it still fail with your initial setup?
t
yeah, always fail on latest version, it used to work in some old one
s
Very interesting. I can't imagine what the difference really is in the two scenarios. If anything, in-between running the before code and the test code itself, there may be something happening which then let's the ViewModel actually use the correct main dispatcher. While if it's done in the same block, somehow the setMain isn't "complete" yet and doesn't take effect. Somehow magically
In the setMain, can you do something like set an extra CoroutineContext key, and then in your ViewModel try and get the context and try to extract that key, so that you can tell if the one you set in setMain is in fact the one you get in your ViewModel? You can I think add a name to your coroutine context and test it that way right?
t
I can name the dispatcher, can I get the name of the dispatcher from inside a scope?
s
Hmmm, is there some way you can extract that out of there somehow? If you do .coroutineContext on it? Not on the keyboard rn can't check 👀
t
Maybe there’s a way but I couldn’t figure
s
Alright yeah sorry I can't help more, but nonetheless this looks like a bug if anything. I can't imagine how one should work but the other approach shouldn't. At least not with what I know right now
t
I got some response in the coroutine repo that
runTest
automatically runs everything queued. With this piece of info I changed my test to this and it worked:
Copy code
@Test
fun test() {
    assert(viewModel.state.value == "A")
    runTest {
        advanceUntilIdle()
        assert(viewModel.state.value == "B")
    }
}
Thanks for the help @Dmitry Khalanskiy [JB]!
s
Alright that makes sense then! Thanks for sharing
👍🏽 1