带有 viewModelScope 的 Kotlin 协程单元测试流程集合

sun*_*son 9 kotlin kotlin-coroutines

我想测试我的 ViewModel 收集 Flow 的方法。在收集器内部,一个 LiveData 对象发生了变异,我想最后检查一下。这大致是设置的样子:

//Outside viewmodel
val f = flow { emit("Test") }.flowOn(Dispatchers.IO)

//Inside viewmodel
val liveData = MutableLiveData<String>()

fun action() {
    viewModelScope.launch { privateAction() }
}

suspend fun privateAction() {
    f.collect {
        liveData.value = it
    }
}
Run Code Online (Sandbox Code Playgroud)

当我现在action()在单元测试中调用该方法时,测试在收集流之前完成。这是测试的样子:

@Test
fun example() = runBlockingTest {
    viewModel.action()
    assertEquals(viewModel.liveData.value, "Test")
}
Run Code Online (Sandbox Code Playgroud)

我正在通过这个 Junit5 扩展以及 LiveData 的即时执行器扩展使用 TestCoroutineDispatcher:

    class TestCoroutineDispatcherExtension : BeforeEachCallback, AfterEachCallback, ParameterResolver {
    @SuppressLint("NewApi") // Only used in unit tests
    override fun supportsParameter(parameterContext: ParameterContext?, extensionContext: ExtensionContext?): Boolean {
        return parameterContext?.parameter?.type === testDispatcher.javaClass
    }

    override fun resolveParameter(parameterContext: ParameterContext?, extensionContext: ExtensionContext?): Any {
        return testDispatcher
    }

    private val testDispatcher = TestCoroutineDispatcher()

    override fun beforeEach(context: ExtensionContext?) {
        Dispatchers.setMain(testDispatcher)
    }

    override fun afterEach(context: ExtensionContext?) {
        Dispatchers.resetMain()
        testDispatcher.cleanupTestCoroutines()
    }
}

    class InstantExecutorExtension : BeforeEachCallback, AfterEachCallback {
    override fun beforeEach(context: ExtensionContext?) {
        ArchTaskExecutor.getInstance()
            .setDelegate(object : TaskExecutor() {
                override fun executeOnDiskIO(runnable: Runnable) = runnable.run()
                override fun postToMainThread(runnable: Runnable) = runnable.run()
                override fun isMainThread(): Boolean = true
            })
    }

    override fun afterEach(context: ExtensionContext?) {
        ArchTaskExecutor.getInstance().setDelegate(null)
    }
}
Run Code Online (Sandbox Code Playgroud)

sun*_*son 0

所以我最终所做的只是将 Dispatcher 传递给 viewmodel 构造函数:

class MyViewModel(..., private val dispatcher = Dispatchers.Main)
Run Code Online (Sandbox Code Playgroud)

然后像这样使用它:

viewModelScope.launch(dispatcher) {}
Run Code Online (Sandbox Code Playgroud)

ViewModel因此,现在当我在测试中使用 a实例化TestCoroutineDispatcher,然后提前时间、使用testCoroutineDispatcher.runBlockingTest {}等时,我可以覆盖它。