单位测试Android ViewModel具有从另一个状态流量绘制的状态流,但从未触发Maplatest

发布于 2025-02-10 20:07:50 字数 4820 浏览 2 评论 0原文

因此,我有一个正在尝试进行单元测试的ViewModel。它正在使用Statein运算符。我发现了有关如何使用StateSin Operator https://developer.android.android。 com/kotlin/flow/test 但是,即使我收集了流量,Maplatest也永远不会触发。

class DeviceConfigurationViewModel(
    val systemDetails: SystemDetails,
    val step: AddDeviceStep.ConfigureDeviceStep,
    val service: DeviceRemoteService
) : ViewModel(), DeviceConfigurationModel {

    @OptIn(ExperimentalCoroutinesApi::class)
    private val _state: StateFlow<DeviceConfigurationModel.State> =
        service.state
            .mapLatest { state ->
                when (state) {
                    DeviceRemoteService.State.Connecting -> {
                        DeviceConfigurationModel.State.Connecting
                    }
                    is DeviceRemoteService.State.ConnectedState.Connected -> {
                        state.sendCommand(step.toCommand(systemDetails))
                        DeviceConfigurationModel.State.Connected
                    }
                    is DeviceRemoteService.State.ConnectedState.CommandSent -> {
                        DeviceConfigurationModel.State.Configuring
                    }
                    is DeviceRemoteService.State.ConnectedState.MessageReceived -> {
                        transformMessage(state)
                    }
                    is DeviceRemoteService.State.Disconnected -> {
                        transformDisconnected(state)
                    }
                }
            }
            .distinctUntilChanged()
            .stateIn(
                viewModelScope,
                SharingStarted.WhileSubscribed(5000), // Keep it alive for a bit if the app is backgrounded
                DeviceConfigurationModel.State.Disconnected
            )

    override val state: StateFlow<DeviceConfigurationModel.State>
        get() = _state

    private fun transformDisconnected(
        state: DeviceRemoteService.State.Disconnected
    ): DeviceConfigurationModel.State {
        return if (state.hasCause) {
            DeviceConfigurationModel.State.UnableToConnect(state)
        } else {
            state.connect()
            DeviceConfigurationModel.State.Connecting
        }
    }

    private fun transformMessage(state: DeviceRemoteService.State.ConnectedState.MessageReceived): DeviceConfigurationModel.State {
        return when (val message = state.message) {
            is Message.AddedToProject -> DeviceConfigurationModel.State.Configured
            is Message.ConfigWifiMessage -> {
                if (!message.values.success) {
                    DeviceConfigurationModel.State.Error(
                        message.values.errorCode,
                        state,
                        step.toCommand(systemDetails)
                    )
                } else {
                    DeviceConfigurationModel.State.Configuring
                }
            }
        }
    }

}

这是我的单位测试。即使我正在收集流程,Maplatest似乎也不会触发。我在这里使用这些建议 https://developer.android.com/kotlin/kotlin/kotlin/flow/flofl/test/test

@OptIn(ExperimentalCoroutinesApi::class)
class DeviceConfigurationViewModelTest {

    private val disconnectedService = mock<DisconnectedService>()

    private val deviceServiceState: MutableStateFlow<DeviceRemoteService.State> =
        MutableStateFlow(DeviceRemoteService.State.Disconnected(disconnectedService, Exception()))

    private val deviceService = mock<DeviceRemoteService> {
        on { state } doReturn deviceServiceState
    }

    private val systemDetails = mock<SystemDetails> {

        on { controllerAddress } doReturn "192.168.1.112"

        on { controllerName } doReturn "000FFF962FE7"

    }

    private val step = AddDeviceDeviceStep.ConfigureDeviceStep(
        44,
        "Thou Shalt Not Covet Thy Neighbor’s Wifi",
        "testing616"
    )

    private lateinit var viewModel: DeviceConfigurationViewModel

    @Before
    fun setup() {
        viewModel = DeviceConfigurationViewModel(systemDetails, step, deviceService)
    }

    @Test
    fun testDeviceServiceDisconnectWithCauseMapsToUnableToConnect() =
        runTest {

            val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.state.collect() }

            deviceServiceState.emit(
                DeviceRemoteService.State.Disconnected(Exception("Something bad happened"))
            )

            assertThat(viewModel.state.value).isInstanceOf(DeviceConfigurationModel.State.UnableToConnect::class.java)
            collectJob.cancel()
        }

}

So I have a ViewModel I'm trying to unit test. It is using the stateIn operator. I found this documentation about how to test stateflows created using the stateIn operator https://developer.android.com/kotlin/flow/test but the mapLatest never triggers even though I'm collecting the flow.

class DeviceConfigurationViewModel(
    val systemDetails: SystemDetails,
    val step: AddDeviceStep.ConfigureDeviceStep,
    val service: DeviceRemoteService
) : ViewModel(), DeviceConfigurationModel {

    @OptIn(ExperimentalCoroutinesApi::class)
    private val _state: StateFlow<DeviceConfigurationModel.State> =
        service.state
            .mapLatest { state ->
                when (state) {
                    DeviceRemoteService.State.Connecting -> {
                        DeviceConfigurationModel.State.Connecting
                    }
                    is DeviceRemoteService.State.ConnectedState.Connected -> {
                        state.sendCommand(step.toCommand(systemDetails))
                        DeviceConfigurationModel.State.Connected
                    }
                    is DeviceRemoteService.State.ConnectedState.CommandSent -> {
                        DeviceConfigurationModel.State.Configuring
                    }
                    is DeviceRemoteService.State.ConnectedState.MessageReceived -> {
                        transformMessage(state)
                    }
                    is DeviceRemoteService.State.Disconnected -> {
                        transformDisconnected(state)
                    }
                }
            }
            .distinctUntilChanged()
            .stateIn(
                viewModelScope,
                SharingStarted.WhileSubscribed(5000), // Keep it alive for a bit if the app is backgrounded
                DeviceConfigurationModel.State.Disconnected
            )

    override val state: StateFlow<DeviceConfigurationModel.State>
        get() = _state

    private fun transformDisconnected(
        state: DeviceRemoteService.State.Disconnected
    ): DeviceConfigurationModel.State {
        return if (state.hasCause) {
            DeviceConfigurationModel.State.UnableToConnect(state)
        } else {
            state.connect()
            DeviceConfigurationModel.State.Connecting
        }
    }

    private fun transformMessage(state: DeviceRemoteService.State.ConnectedState.MessageReceived): DeviceConfigurationModel.State {
        return when (val message = state.message) {
            is Message.AddedToProject -> DeviceConfigurationModel.State.Configured
            is Message.ConfigWifiMessage -> {
                if (!message.values.success) {
                    DeviceConfigurationModel.State.Error(
                        message.values.errorCode,
                        state,
                        step.toCommand(systemDetails)
                    )
                } else {
                    DeviceConfigurationModel.State.Configuring
                }
            }
        }
    }

}

And here's my unit test. The mapLatest never seems to get triggered even though I'm collecting the flow. I'm using the suggestions here https://developer.android.com/kotlin/flow/test

@OptIn(ExperimentalCoroutinesApi::class)
class DeviceConfigurationViewModelTest {

    private val disconnectedService = mock<DisconnectedService>()

    private val deviceServiceState: MutableStateFlow<DeviceRemoteService.State> =
        MutableStateFlow(DeviceRemoteService.State.Disconnected(disconnectedService, Exception()))

    private val deviceService = mock<DeviceRemoteService> {
        on { state } doReturn deviceServiceState
    }

    private val systemDetails = mock<SystemDetails> {

        on { controllerAddress } doReturn "192.168.1.112"

        on { controllerName } doReturn "000FFF962FE7"

    }

    private val step = AddDeviceDeviceStep.ConfigureDeviceStep(
        44,
        "Thou Shalt Not Covet Thy Neighbor’s Wifi",
        "testing616"
    )

    private lateinit var viewModel: DeviceConfigurationViewModel

    @Before
    fun setup() {
        viewModel = DeviceConfigurationViewModel(systemDetails, step, deviceService)
    }

    @Test
    fun testDeviceServiceDisconnectWithCauseMapsToUnableToConnect() =
        runTest {

            val collectJob = launch(UnconfinedTestDispatcher()) { viewModel.state.collect() }

            deviceServiceState.emit(
                DeviceRemoteService.State.Disconnected(Exception("Something bad happened"))
            )

            assertThat(viewModel.state.value).isInstanceOf(DeviceConfigurationModel.State.UnableToConnect::class.java)
            collectJob.cancel()
        }

}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

千纸鹤 2025-02-17 20:07:50

我相信这是因为ViewModelsCope使用引擎盖下的硬编码主调度器。

您可以按照说明在Android文档中的此处如何为测试设置主要调度程序。

I believe this is happening because the viewModelScope uses a hardcoded Main dispatcher under the hood.

You can follow the instructions here in the Android documentation to see how you can to set the Main dispatcher for tests.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文