thread here
# coroutines
z
thread here
@Sam your problem is
uiDispatcher : CoroutineDispatcher = Main
change it to
uiDispatcher : CoroutineDispatcher
then you cannot inject the wrong dispatcher
or better:
s
Main was a default for application run
z
dispatcher : CoroutineContext
s
in tests, i do override it
s
Did you try using the
TestCoroutineContext
, which has a Dispatcher that you can control by moving virtual time?
s
Copy code
private val viewModel = MainFragmentViewModel( appsRepository, Dispatchers.Unconfined )
z
I use
Dispatchers.Default
for my view models
s
No, i haven't used TestCoroutineContext
z
but you can use
Dispatchers.Main
if you prefer
s
But again, i don't think the problem is with the dispatcher
the code is like this
unit test
Copy code
runBlocking {}
calling view model function (regular function)
z
mockito will give weird failrues
if something isn’t mocked properly
it’s often non-obvious
s
which is launching another coroutine using the view model scope
this coroutine invokes the actual suspend function
s
Which suspend function?
s
in unit test, i'm trying to verify that the suspend function is actually invoked
Copy code
suspend fun updateCurrentInstalledApps( installedPackages : List<String> ) = withContext( IO ) {
       appsDao.updateCurrentInstalledApps( installedPackages )
    }
s
Your unit test code is an empty `runBlocking {}`…
s
here is the actual code for test
Copy code
@Test
    fun `onDisableAllAppsClicked is relayed to repository`() {

        runBlocking {
            viewModel.onDisableAllAppsClicked()
            verify( appsRepository ).updateAllAppsExcludedStatus( true )
        }
    }
Copy code
private val appsRepository = mock<AppsRepository>()
    private val viewModel = MainFragmentViewModel( appsRepository, Dispatchers.Unconfined )
s
This test just launches the coroutine in your ViewModel and then immediately return. There is no blocking code in your test-code (not in your test, not in your Viewmodel)
s
yes, thats another problem
how do i wait for the launched coroutine to finish first?
s
You use the
<http://Dispatchers.IO|Dispatchers.IO>
, which will run on a background thread. This one doesn’t do really anything, since your test-function returns almost immediately.
Inject your Dispatchers, both for
Main
and
IO
. Either use, for your tests, the Unconfined one or use the TestCoroutineContext for more granular control.
s
In my case, IO is not directly used in viewmodel, instead is used in repository. Anyways, keeping that aside, wouldn't the continuation still be different?
and by continuation, i'm talking about the hidden parameter passed to the suspend function hosted in my repository
s
That’s right… you only check if the
updateAllAppsExcludedStatus
is called or not… I wonder if the
suspend
is not handled well by Mockito, since the actual JVM signature is quire different from when it would have been non-suspend.
s
yeah, mockito just uses it as yet another parameter and fails as the one used in verify will be different from the one that was actually invoked
runBlocking{...} vs viewModelScope.launch{ ... }
s
Do you use Mockito or Mockito-Kotlin?
s
tried both
Copy code
testImplementation 'org.mockito:mockito-core:2.22.0'
    testImplementation 'com.nhaarman:mockito-kotlin:1.5.0'
s
s
Thanks, version 2 did it
with this, my test works as expected
Copy code
testCompile 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.0.0'
though i still have to work on making sure launched coroutines finish first
Thanks a ton @streetsofboston
s
Glad that it worked out for you! 🙂
d
Even though it might be fixed, you can always switch to mockk.io, it's much better for Kotlin and coroutines... @Sam
s
Thx, i had initially looked at it just to open classes but found a kotlin compiler plugin to workaround. I will try this one out.