https://kotlinlang.org logo
Title
a

Andrew Steinmetz

09/21/2021, 9:21 PM
Hey everyone, I was trying to learn how to unit test a store in MVI using the
SuspendExecutor
and was wondering if anybody had a sample project where they could get a
runBlockingTest
to work? After some research it seems like the
runBlockingTest
in the coroutines test library is just supported on the JVM, but there is a workaround to get it to work per this comment. After adding that to my project, I still seem to run into a few issues when trying to run the test.
import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory
import com.plusmobileapps.shared.db.Activity
import com.plusmobileapps.wolfpack.activity.list.ActivityListStoreProvider
import com.plusmobileapps.wolfpack.di.DEFAULT_DISPATCHER_TAG
import com.plusmobileapps.wolfpack.di.MAIN_DISPATCHER_TAG
import com.plusmobileapps.wolfpack.mocks.ActivityRepositoryMock
import com.plusmobileapps.wolfpack.runBlockingTest
import kotlinx.coroutines.Dispatchers
import org.kodein.di.DI
import org.kodein.di.bind
import org.kodein.di.singleton
import kotlin.coroutines.CoroutineContext
import kotlin.test.Test
import kotlin.test.assertEquals

class ActivityListStoreTest {
    private val storeFactory = DefaultStoreFactory
    private val repository = ActivityRepositoryMock()

    private val di = DI {
        bind<CoroutineContext>(MAIN_DISPATCHER_TAG) { singleton { testCoroutineContext } }
        bind<CoroutineContext>(DEFAULT_DISPATCHER_TAG) { singleton { testCoroutineContext } }
        bind<ActivityRepository> { singleton { repository } }
        bind<StoreFactory> { singleton { storeFactory } }
    }

    @Test
    fun foo() = runBlockingTest {
        val activity = Activity(1L, "", true)
        repository.addActivity(activity)
        val store = ActivityListStoreProvider(di).provide()

        assertEquals(listOf(activity), store.state.activities)
    }
}
• For ios, keep getting an
InvalidMutabilityException
which I think is because the DI gets frozen inside the test because once something is retrieved from DI it throws the exception? • Then for android, keep getting
java.lang.RuntimeException: Method getMainLooper in android.os.Looper not mocked.
which am I supposed to import the coroutine test library on jvm tests to get them to work? I'm a little lost in the weeds trying to figure this out and might even consider switching to Reaktive as it seems to have a pretty good testing support from looking at the todo sample tests. Any help is greatly appreciated!
a

Arkadii Ivanov

09/21/2021, 10:11 PM
Hello! Re issue 1: I never used any DI in KMP, but indeed the
DI
containers seems like captured by
runBlockingTest
and frozen. I usually supply dependencies via direct arguments to a Store factories. You can try creating
DI
inside the
runBlockingTest
block. Re issue 2: You should avoid using the Main dispatcher in unit tests. It's not clear what is the
testCoroutineContext
in your case. You can try using
Dispatchers.Unconfined
.
Just noticed the words about Reaktive. Indeed it has good testing support. From my point it's easier to test and more predictable. Specially because it has TestScheduler, and also because unit tests are always running synchronously. Coroutines also have TestDispatcher btw, but only for JVM.
a

Andrew Steinmetz

09/21/2021, 11:08 PM
Thanks for the reply, It does seem like
runBlockingTest {}
freezes anything inside the block because removing
DI
and injecting the properties does fix the the
InvalidMutabilityException
on the original crash from storefactory, however if I try to touch the repository mock it will freeze it and then throw an exception on that 🙃
testCoroutineContext
was something I found as a workaround in this github issue since that
TestDispatcher
is a JVM only library. I was hoping to use coroutines since I was also writing a backend using Ktor which uses coroutines, but I might just have the frontend work off of Reaktive since coroutines multiplatform testing has no official support yet.
a

Arkadii Ivanov

09/21/2021, 11:25 PM
There must be another place which freezes your repository. It should be either freezable (e.g. Ktor Client should work fine even if frozen), or you should make sure you never freeze it. You can add expect/actual
ensureNeverFrozen
function and call it on your repository before passing to the Store. It will throw an exception when the repository got frozen, not when it is used later. You can try using Dispatchers.Unconfined and pass it to the Store and to its SuspendExecutor and/or SuspendBootstrapper. By default they both use Dispatchers.Main.
a

Andrew Steinmetz

09/22/2021, 4:37 PM
I honestly just ripped out coroutines in my project and replaced it with Reaktive, Already way happier with the testing support on there compared to coroutines and appreciate the help. I'm sure I'm going to have more questions in #reaktive now to learn how to use Rx better since its such an extensive set of operators!
a

Arkadii Ivanov

09/22/2021, 4:38 PM
Nice move! Feel free to ask questions 😃
:thank-you: 1