10

質問

  1. viewModelScopeKotlinコルーチンを使用してAndroid単体テストに注入するための最良の戦略は何ですか?

  2. 単体テストのために CoroutineScope が ViewModel に挿入されるとき、CoroutineDispatcher も挿入されflowOn、実稼働コードで必要とされない場合でも使用して定義されるべきですか?

flowOnDispatchers.IORetrofitはSomeRepository.ktでスレッド化を処理し、viewModelScopeは でデータを返すため、このユース ケースのプロダクション コードでは必要ありませんDispathers.Main。どちらもデフォルトで行われます。

期待される

Kotlin Flow 値に保存された Android の ViewModel ビューステート値で単体テストを実行します。

観察した

メイン ディスパッチャを含むモジュールの初期化に失敗しました。テストには、kotlinx-coroutines-test モジュールの Dispatchers.setMain を使用できます

CoroutineScope がハードコードされている最初の発生時に、単体テストが失敗します。viewModelScope起動されたコルーチンが ViewModel のライフサイクルを維持するように利用されます。ただし、viewModelScopeViewModel 内から作成されるため、ViewModel の外部で定義して引数として渡すことができる CoroutineDispatcher と比較して、注入がより複雑になります。

実装

SomeViewModel.kt

fun bindIntents(view: FeedView) {
    view.initStateIntent().onEach {
        initState(view)
    }.launchIn(viewModelScope)        
}

SomeTest.kt

@ExperimentalCoroutinesApi
class SomeTest : BeforeAllCallback, AfterAllCallback {

    private val testDispatcher = TestCoroutineDispatcher()
    private val testScope = TestCoroutineScope(testDispatcher)
    private val repository = mockkClass(FeedRepository::class)
    private var loadNetworkIntent = MutableStateFlow<LoadNetworkIntent?>(null)

    override fun beforeAll(context: ExtensionContext?) {
        // Set Coroutine Dispatcher.
        Dispatchers.setMain(testDispatcher)
    }

    override fun afterAll(context: ExtensionContext?) {
        Dispatchers.resetMain()
        // Reset Coroutine Dispatcher and Scope.
        testDispatcher.cleanupTestCoroutines()
        testScope.cleanupTestCoroutines()
    }

    @Test
    fun topCafesPoc() = testDispatcher.runBlockingTest {
        coEvery {
            repository.getInitialCafes(any())
        } returns mockGetInitialCafes(mockCafesList, SUCCESS)

        val viewModel = FeedViewModel(repository)
        viewModel.bindIntents(object : FeedView {
            @ExperimentalCoroutinesApi
            override fun initStateIntent() = MutableStateFlow(true)

            @ExperimentalCoroutinesApi
            override fun loadNetworkIntent() = loadNetworkIntent.filterNotNull()

            override fun render(viewState: FeedViewState) {
                // TODO: Test viewState
            }

        })
        loadNetworkIntent.value = LoadNetworkIntent(true)
        // TODO
        // assertEquals(4, 2 + 2)
    }
}

注: JUnit 5 テスト拡張機能は、最終バージョンで使用されます。

完全なエラー ログ

スレッド「main @coroutine#1」での例外 java.lang.IllegalStateException: メイン ディスパッチャを含むモジュールの初期化に失敗しました。テストでは、kotlinx-coroutines-test モジュールの Dispatchers.setMain を kotlinx.coroutines.internal.MissingMainCoroutineDispatcher.missing(MainDispatchers.kt:113) で使用できます。 kotlinx.coroutines.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:285) kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:26) kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:109) at kotlinx.coroutines .AbstractCoroutine.start(AbstractCoroutine.kt:158) at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders. 68) com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33) で com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230) で com.intellij.rt.junit で.JUnitStarter.main(JUnitStarter.java:58) 原因: java.lang.RuntimeException: android.os.Looper のメソッド getMainLooper がモックされていません。見るhttp://g.co/androidstudio/not-mocked詳細については。android.os.Looper.getMainLooper(Looper.java) で kotlinx.coroutines.android.AndroidDispatcherFactory.createDispatcher(HandlerDispatcher.kt:55) で kotlinx.coroutines.android.AndroidDispatcherFactory.createDispatcher(HandlerDispatcher.kt:52) で kotlinx. coroutines.internal.MainDispatchersKt.tryCreateDispatcher(MainDispatchers.kt:57) at kotlinx.coroutines.test.internal.TestMainDispatcher.getDelegate(MainTestDispatcher.kt:19) at kotlinx.coroutines.test.internal.TestMainDispatcher.getImmediate(MainTestDispatcher.kt: 32) androidx.lifecycle.ViewModelKt.getViewModelScope(ViewModel.kt:42) で ... スレッド「main @coroutine#1」で 40 以上の例外 java.lang.IllegalStateException: Main ディスパッチャを持つモジュールの初期化に失敗しました。テストディスパッチャ用。com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58) の prepareStreamsAndStart(JUnitStarter.java:230) 原因: java.lang.RuntimeException: android.os.Looper のメソッド getMainLooper がモックされていません。見る詳細については、http: //g.co/androidstudio/not-mockedをご覧ください。android.os.Looper.getMainLooper(Looper.java) で kotlinx.coroutines.android.AndroidDispatcherFactory.createDispatcher(HandlerDispatcher.kt:55) で kotlinx.coroutines.android.AndroidDispatcherFactory.createDispatcher(HandlerDispatcher.kt:52) で kotlinx. coroutines.internal.MainDispatchersKt.tryCreateDispatcher(MainDispatchers.kt:57) at kotlinx.coroutines.test.internal.TestMainDispatcher.getDelegate(MainTestDispatcher.kt:19) at kotlinx.coroutines.test.internal.TestMainDispatcher.getImmediate(MainTestDispatcher.kt: 32) app.topcafes.feed.viewmodel.FeedViewModel.bindIntents(FeedViewModel.kt:38) で androidx.lifecycle.ViewModelKt.getViewModelScope(ViewModel.kt:42) ... 39 もっと見る

4

1 に答える 1