我正在try 为我的应用程序编写单元测试.我正在使用Mockk来模拟课堂.

这是我的单元测试

@OptIn(ExperimentalCoroutinesApi::class)
class MainViewModalTest {

    private lateinit var viewModel: MainViewModal
    private val savedStateHandle = mockk<SavedStateHandle>(relaxed = true)
    private val randomFactUseCase = mockk<RandomFactUseCase>()

    @get:Rule
    val mainDispatcher = MainDispatcherRule()


    @Before
    fun setup() {
        viewModel = MainViewModal(randomFactUseCase, savedStateHandle)
    }

    @Test
    fun `When fetchRandomFact succeeds, update UI state with fact`() = runBlockingTest {
        // Given
        coEvery { randomFactUseCase.invoke() } returns Result.Success(RandomFact(data = listOf("Some interesting fact")), 200)

        // When
        viewModel.fetchRandomFact()

        // Then
        val expectedUiState = UiState(loading = false, fact = "Some interesting fact", error = "")
        assert(viewModel.uiState.value == expectedUiState)
    }
}
class MainDispatcherRule(private val testDispatcher: TestDispatcher = UnconfinedTestDispatcher()) :
    TestWatcher() {

    override fun starting(description: Description) {
        super.starting(description)
        Dispatchers.setMain(testDispatcher)
    }

    override fun finished(description: Description) {
        super.finished(description)
        Dispatchers.resetMain()
    }
}

它失败,并出现此错误

线程"Test Worker@Coroutine#1"中出现异常 Io.moockk.MockKException:找不到以下项的答案 RandomFactUseCase(#2).Invoke(继续{})在配置的 答案:()

这是我的viewModel:

@HiltViewModel
class MainViewModal @Inject constructor(
    private val randomFactUseCase: RandomFactUseCase,
    private val savedStateHandle: SavedStateHandle,

    ) : ViewModel() {
    init {
        fetchRandomFact()
    }

    val uiState: StateFlow<UiState> = savedStateHandle.getStateFlow("fact", UiState(loading = true))

    fun fetchRandomFact() {
        viewModelScope.launch {
            setState(loading = true)
            when (val response = randomFactUseCase()) {
                is Result.Success -> {
                    val fact =
                        if (response.data?.data?.isNotEmpty() == true) response.data.data[0] else ""
                    setState(
                        loading = false,
                        fact = fact,
                        error = if (fact.isBlank()) "No fact available!" else ""
                    )
                }

                is Result.Error -> {
                    setState(
                        loading = false,
                        fact = "",
                        error = response.exception.message
                            ?: "Something went wrong! Please try again"
                    )
                }
            }
        }
    }

    private fun setState(loading: Boolean = true, fact: String = "", error: String = "") {
        savedStateHandle["fact"] = UiState(loading = loading, fact = fact, error = error)
    }
}

@Parcelize
data class UiState(val loading: Boolean = true, val fact: String = "", val error: String = "") :
    Parcelable

不知道我在这里错过了什么.虽然我觉得我已经很接近了.

EDIT1: 根据@broots的建议,我已经按如下方式更新了viewModel:

@HiltViewModel
class MainViewModal @Inject constructor(
    private val randomFactUseCase: RandomFactUseCase,
    private val savedStateHandle: SavedStateHandle,

    ) : ViewModel() {
    init {
        invokeFunction()
    }

    fun invokeFunction() {
        viewModelScope.launch {
            fetchRandomFact()
        }
    }

    val uiState: StateFlow<UiState> = savedStateHandle.getStateFlow("fact", UiState(loading = true))

    suspend fun fetchRandomFact() {
        setState(loading = true)
        when (val response = randomFactUseCase()) {
            is Result.Success -> {
                val fact =
                    if (response.data?.data?.isNotEmpty() == true) response.data.data[0] else ""
                setState(
                    loading = false,
                    fact = fact,
                    error = if (fact.isBlank()) "No fact available!" else ""
                )
            }

            is Result.Error -> {
                setState(
                    loading = false,
                    fact = "",
                    error = response.exception.message ?: "Something went wrong! Please try again"
                )
            }
        }
    }

    private fun setState(loading: Boolean = true, fact: String = "", error: String = "") {
        savedStateHandle["fact"] = UiState(loading = loading, fact = fact, error = error)
    }
}

这并没有解决问题.(新增此内容以供参考)

推荐答案

你正在使用测试调度器,它在被调用时直接执行操作,并且在你的视图模型中,你在构造函数中调用了usecase. 这样,mock被称为仍然在@Before块内

如果您想要在测试体中定义模拟响应,那么您可以将view Model变量设置为LAZY

    private val savedStateHandle = mockk<SavedStateHandle>(relaxed = true)
    private val randomFactUseCase = mockk<RandomFactUseCase>()

    private val viewModel: MainViewModal by lazy {
        MainViewModal(randomFactUseCase, savedStateHandle)
    }

    @Test
    fun `When fetchRandomFact succeeds, update UI state with fact`() = runBlockingTest {
        // Given
        coEvery { randomFactUseCase.invoke() } returns Result.Success(RandomFact(data = listOf("Some interesting fact")), 200)

        // When
        viewModel // dereferencing lazy property will call constructor, which calls method under test

        // Then
        val expectedUiState = UiState(loading = false, fact = "Some interesting fact", error = "")
        assert(viewModel.uiState.value == expectedUiState)
    }

Android相关问答推荐

懒惰列的滚动到项目不按预期工作'

如何从Android 12的来电中获取电话号码?

安卓喷气背包组成倒计时动画

如何在Jetpack Compose android中使用导航

页面更改时不显示 cogo toast 消息

如何在Jetpack Compose中向SearchBar添加边框

Android Studio SQLite 错误:列不正确(没有这样的列:_id)

Android 导航 - 定义参数

Android Jetpack Compose 电视焦点恢复

如何在 Android 的 ViewModel 中使用 LiveData

具有管理员权限的 Kotlin 中的多用户系统

CoroutineScope 与挂起函数

为什么集成React Native时compileSdkVersion错误?

Jetpack Compose 部分或开放侧边框

在 Kotlin 中打开新片段时如何对当前片段应用更改?

清洁架构中的服务

运行一次 kotlin 流,但在下游收到两次

在alert 对话框生成器中启动协程

如何在stroke android drawable中设置渐变

未使用的内容填充参数