JUnit 5 测试 - 无法观察嵌套的 LiveData 值

JUnit 5 Test - Unable to Observe Nested LiveData Value

概述

预期 - 在本地单元测试中保存嵌套的 LiveData 值,然后断言它们的值。

Observed - 在生产代码中成功观察到在 ViewModel 中保存嵌套的 LiveData 值,但在本地单元测试中失败。这可能是由于本地单元测试中缺少线程,而 Android 环境中的 运行。

代码

ContentViewModel.kt

    is ContentSelected -> {
        _feedViewState.value = _feedViewState.value?.copy(

                // LiveData value for ContentToPlay initiated here.
                contentToPlay = switchMap(getAudiocast(contentSelected)) { lce ->
                    liveData {
                        when (lce) {
                            is Loading ->
                                _viewEffect.value = _viewEffect.value?.copy(
                                        notifyItemChanged = liveData {
                                            emit(Event(NotifyItemChangedEffect(...)))
                                        })
                            is Lce.Content -> {
                                _viewEffect.value = _viewEffect.value?.copy(
                                        notifyItemChanged = liveData {
                                            emit(Event(NotifyItemChangedEffect(...)))
                                        })

                                // LiveData value for ContentToPlay saved here.
                                emit(Event(lce.packet))
                            }
                            is Error -> {
                                _viewEffect.value = _viewEffect.value?.copy(
                                        notifyItemChanged = liveData {
                                            emit(Event(NotifyItemChangedEffect(...)))
                                        })
                                _viewEffect.value = _viewEffect.value?.copy(
                                        snackBar = liveData {
                                            emit(Event(SnackBarEffect(...)))
                                        })
                            }
                        }
                    }
                })
        ...
    }

PlayContentTests.kt


    @ExtendWith(InstantExecutorExtension::class)
    class PlayContentTests {
        @ParameterizedTest
        @MethodSource("FeedLoad")
        fun `Play Content`(test: PlayContentTest) = runBlocking {
            // ViewModel method included to initiate ContentSelected event.
            ...
            when (test.lceState) {
                LOADING ->
                    assertThat(contentViewModel.viewEffect.getOrAwaitValue().notifyItemChanged.getOrAwaitValue().peekEvent()).isEqualTo(
                            NotifyItemChangedEffect(...))
                CONTENT -> {
                    assertThat(contentViewModel.feedViewState.getOrAwaitValue().contentToPlay.getOrAwaitValue().peekEvent()).isEqualTo(
                            ContentToPlay(...))
                    assertThat(contentViewModel.viewEffect.getOrAwaitValue().notifyItemChanged.getOrAwaitValue().peekEvent()).isEqualTo(
                            NotifyItemChangedEffect(...))
                }
                ERROR -> {
                    assertThat(contentViewModel.feedViewState.getOrAwaitValue().contentToPlay.getOrAwaitValue().peekEvent()).isEqualTo(
                            ContentToPlay(...))
                    assertThat(contentViewModel.viewEffect.getOrAwaitValue().notifyItemChanged.getOrAwaitValue().peekEvent()).isEqualTo(
                            NotifyItemChangedEffect(...))
                }
            }
        }
    }

在所有 (LCE) 加载、内容、错误条件下保存 LiveData

在 ViewModel 的所有 LCE 条件中保存 ContentToPlay LiveData 值或 null 以确保为本地单元测试同步返回值。

注意 - 此策略的缺点是它会向生产代码中的视图发出不必要的 ContentToPlay 值,这并不理想,但看起来并不理想一个主要问题。

ContentViewModel.kt

    _feedViewState.value = _feedViewState.value?.copy(contentToPlay =
    switchMap(getAudiocast(contentSelected)) { lce ->
        liveData {
            when (lce) {
                is Loading -> {
                    setContentLoadingStatus(contentSelected.content.id, View.VISIBLE)
                    _viewEffect.value = _viewEffect.value?.copy(
                            notifyItemChanged = liveData {
                                emit(Event(NotifyItemChangedEffect(contentSelected.position)))
                            })
                    // Empty ContentToPlay saved.
                    emit(Event(null))
                }
                is Lce.Content -> {
                    setContentLoadingStatus(contentSelected.content.id, View.GONE)
                    _viewEffect.value = _viewEffect.value?.copy(
                            notifyItemChanged = liveData {
                                emit(Event(NotifyItemChangedEffect(contentSelected.position)))
                            })
                    // ContentToPlay saved.
                    emit(Event(lce.packet))
                }
                is Error -> {
                    setContentLoadingStatus(contentSelected.content.id, View.GONE)
                    _viewEffect.value = _viewEffect.value?.copy(
                            notifyItemChanged = liveData {
                                emit(Event(NotifyItemChangedEffect(contentSelected.position)))
                            })
                    if (lce.packet.filePath.equals(TTS_CHAR_LIMIT_ERROR))
                        _viewEffect.value = _viewEffect.value?.copy(
                                snackBar = liveData {
                                    emit(Event(SnackBarEffect(TTS_CHAR_LIMIT_ERROR_MESSAGE)))
                                })
                    else _viewEffect.value = _viewEffect.value?.copy(
                            snackBar = liveData {
                                emit(Event(SnackBarEffect(CONTENT_PLAY_ERROR)))
                            })
                    // Empty ContentToPlay saved.
                    emit(Event(null))
                }
            }
        }
    })