使用 StateFlow 对 Android ViewModel 进行单元测试,该 StateFlow 从另一个 StateFlow 映射最新版本,但永远不会触发 mapLatest

ExN*_*ura 4 android kotlin android-viewmodel kotlin-coroutines kotlin-flow

所以我有一个 ViewModel 我正在尝试进行单元测试。它使用 stateIn 运算符。我找到了有关如何测试使用 stateIn 运算符创建的状态流的文档https://developer.android.com/kotlin/flow/test但即使我正在收集流,mapLatest也永远不会触发。

\n
class DeviceConfigurationViewModel(\n    val systemDetails: SystemDetails,\n    val step: AddDeviceStep.ConfigureDeviceStep,\n    val service: DeviceRemoteService\n) : ViewModel(), DeviceConfigurationModel {\n\n    @OptIn(ExperimentalCoroutinesApi::class)\n    private val _state: StateFlow<DeviceConfigurationModel.State> =\n        service.state\n            .mapLatest { state ->\n                when (state) {\n                    DeviceRemoteService.State.Connecting -> {\n                        DeviceConfigurationModel.State.Connecting\n                    }\n                    is DeviceRemoteService.State.ConnectedState.Connected -> {\n                        state.sendCommand(step.toCommand(systemDetails))\n                        DeviceConfigurationModel.State.Connected\n                    }\n                    is DeviceRemoteService.State.ConnectedState.CommandSent -> {\n                        DeviceConfigurationModel.State.Configuring\n                    }\n                    is DeviceRemoteService.State.ConnectedState.MessageReceived -> {\n                        transformMessage(state)\n                    }\n                    is DeviceRemoteService.State.Disconnected -> {\n                        transformDisconnected(state)\n                    }\n                }\n            }\n            .distinctUntilChanged()\n            .stateIn(\n                viewModelScope,\n                SharingStarted.WhileSubscribed(5000), // Keep it alive for a bit if the app is backgrounded\n                DeviceConfigurationModel.State.Disconnected\n            )\n\n    override val state: StateFlow<DeviceConfigurationModel.State>\n        get() = _state\n\n    private fun transformDisconnected(\n        state: DeviceRemoteService.State.Disconnected\n    ): DeviceConfigurationModel.State {\n        return if (state.hasCause) {\n            DeviceConfigurationModel.State.UnableToConnect(state)\n        } else {\n            state.connect()\n            DeviceConfigurationModel.State.Connecting\n        }\n    }\n\n    private fun transformMessage(state: DeviceRemoteService.State.ConnectedState.MessageReceived): DeviceConfigurationModel.State {\n        return when (val message = state.message) {\n            is Message.AddedToProject -> DeviceConfigurationModel.State.Configured\n            is Message.ConfigWifiMessage -> {\n                if (!message.values.success) {\n                    DeviceConfigurationModel.State.Error(\n                        message.values.errorCode,\n                        state,\n                        step.toCommand(systemDetails)\n                    )\n                } else {\n                    DeviceConfigurationModel.State.Configuring\n                }\n            }\n        }\n    }\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这是我的单元测试。即使我正在收集流量,mapLatest 似乎也永远不会被触发。我正在使用此处的建议https://developer.android.com/kotlin/flow/test

\n
@OptIn(ExperimentalCoroutinesApi::class)\nclass DeviceConfigurationViewModelTest {\n\n    private val disconnectedService = mock<DisconnectedService>()\n\n    private val deviceServiceState: MutableStateFlow<DeviceRemoteService.State> =\n        MutableStateFlow(DeviceRemoteService.State.Disconnected(disconnectedService, Exception()))\n\n    private val deviceService = mock<DeviceRemoteService> {\n        on { state } doReturn deviceServiceState\n    }\n\n    private val systemDetails = mock<SystemDetails> {\n\n        on { controllerAddress } doReturn "192.168.1.112"\n\n        on { controllerName } doReturn "000FFF962FE7"\n\n    }\n\n    private val step = AddDeviceDeviceStep.ConfigureDeviceStep(\n        44,\n        "Thou Shalt Not Covet Thy Neighbor\xe2\x80\x99s Wifi",\n        "testing616"\n    )\n\n    private lateinit var viewModel: DeviceConfigurationViewModel\n\n    @Before\n    fun setup() {\n        viewModel = DeviceConfigurationViewModel(systemDetails, step, deviceService)\n    }\n\n    @Test\n    fun testDeviceServiceDisconnectWithCauseMapsToUnableToConnect() =\n        runTest {\n\n            val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.state.collect() }\n\n            deviceServiceState.emit(\n                DeviceRemoteService.State.Disconnected(Exception("Something bad happened"))\n            )\n\n            assertThat(viewModel.state.value).isInstanceOf(DeviceConfigurationModel.State.UnableToConnect::class.java)\n            collectJob.cancel()\n        }\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n

ali*_*ter 5

我相信这种情况正在发生,因为它viewModelScope在幕后使用了硬编码的主调度程序。

您可以按照Android 文档中的说明来了解如何设置主调度程序以进行测试。