当前位置: 首页 > 知识库问答 >
问题:

单元测试新的Kotlin coroutine StateFlow

阮疏珂
2023-03-14

我正在尝试它,在尝试单元测试我的ViewModel时遇到了一个问题。我想要实现的目标:测试我的stateflow在我的ViewModel中以正确的顺序接收所有状态值。

我代码如下:

ViewModel:

class WalletViewModel(private val getUserWallets: GetUersWallets) : ViewModel() {

val userWallet: StateFlow<State<UserWallets>> get() = _userWallets
private val _userWallets: MutableStateFlow<State<UserWallets>> =
    MutableStateFlow(State.Init)

fun getUserWallets() {
    viewModelScope.launch {
        getUserWallets.getUserWallets()
            .onStart { _userWallets.value = State.Loading }
            .collect { _userWallets.value = it }
    }
}
    @Test
fun `observe user wallets ok`() = runBlockingTest {
    Mockito.`when`(api.getAssetWallets()).thenReturn(TestUtils.getAssetsWalletResponseOk())
    Mockito.`when`(api.getFiatWallets()).thenReturn(TestUtils.getFiatWalletResponseOk())

    viewModel.getUserWallets()

    val res = arrayListOf<State<UserWallets>>()
    viewModel.userWallet.toList(res) //doesn't works

    Assertions.assertThat(viewModel.userWallet.value is State.Success).isTrue() //works, last value enmited
}
java.lang.IllegalStateException: This job has not completed yet
    at kotlinx.coroutines.JobSupport.getCompletionExceptionOrNull(JobSupport.kt:1189)
    at kotlinx.coroutines.test.TestBuildersKt.runBlockingTest(TestBuilders.kt:53)
    at kotlinx.coroutines.test.TestBuildersKt.runBlockingTest$default(TestBuilders.kt:45)
    at WalletViewModelTest.observe user wallets ok(WalletViewModelTest.kt:52)
....

编辑:作为一个临时解决方案,我将它作为一个实时数据进行测试:

    @Captor
    lateinit var captor: ArgumentCaptor<State<UserWallets>>

    @Mock
    lateinit var walletsObserver: Observer<State<UserWallets>>

    @Test
    fun `observe user wallets ok`() = runBlockingTest {
        viewModel.userWallet.asLiveData().observeForever(walletsObserver)

        viewModel.getUserWallets()

        captor.run {
            Mockito.verify(walletsObserver, Mockito.times(3)).onChanged(capture())
            Assertions.assertThat(allValues[0] is State.Init).isTrue()
            Assertions.assertThat(allValues[1] is State.Loading).isTrue()
            Assertions.assertThat(allValues[2] is State.Success).isTrue()
        }
    }

共有1个答案

季小云
2023-03-14

SharedFlow/Stateflow是热流,正如文档中所述,共享流之所以称为热流,是因为它的活动实例独立于收集器的存在而存在。这意味着启动流的收集的作用域不会自行完成。

要解决这个问题,您需要取消collect被调用的范围,由于测试的范围是测试本身,取消测试是不合适的,所以您需要的是在另一个作业中启动它。

@Test
fun `Testing a integer state flow`() = runBlockingTest{
    val _intSharedFlow = MutableStateFlow(0)
    val intSharedFlow = _intSharedFlow.asStateFlow()
    val testResults = mutableListOf<Int>()

    val job = launch {
        intSharedFlow.toList(testResults)
    }
    _intSharedFlow.value = 5

    assertEquals(2, testResults.size)
    assertEquals(0, testResults.first())
    assertEquals(5, testResults.last())
    job.cancel()
}

您的特定用例:

@Test
fun `observe user wallets ok`() = runBlockingTest {
    whenever(api.getAssetWallets()).thenReturn(TestUtils.getAssetsWalletResponseOk())
    whenever(api.getFiatWallets()).thenReturn(TestUtils.getFiatWalletResponseOk())

    viewModel.getUserWallets()

    val result = arrayListOf<State<UserWallets>>()
    val job = launch {
        viewModel.userWallet.toList(result) //now it should work
    }

    Assertions.assertThat(viewModel.userWallet.value is State.Success).isTrue() //works, last value enmited
    Assertions.assertThat(result.first() is State.Success) //also works
    job.cancel()
}

两件重要的事情:

  1. 始终取消创建的作业,以避免java.lang.IllegalStateException:此作业尚未完成
  2. 因为这是一个状态流,所以当开始收集时(ToList)您会收到最后一个状态。但是,如果您首先开始收集并在调用函数ViewModel.getUserWallets()之后,那么在result列表中,您将拥有所有状态,以防您也想测试它。
 类似资料:
  • Android Studio 1.1 添加了单元测试支持,详细请看 Unit testing support。本章的其余部分描述的是 “instrumentation tests”。利用 Instrumentation 测试框架可以构建独立的测试 APK 并运行在真实设备(或模拟器)中进行测试。

  • 英文原文:http://emberjs.com/guides/testing/unit/ 单元测试用于测试代码的一个小片段,确保其功能正常。与集成测试不同,单元测试被限定在一个范围内,并且不需要Ember应用运行。 全局 vs 模块 过去如果没有作为一个全局变量加载整个Ember应用,要对应用进行测试非常困难。通过使用模块(CommonJS,AMD等)来编写应用,可以只加载被测试的部分,而不用将其

  • 单元测试涉及测试软件应用程序的每个单元或单个组件。这是第一级软件测试。单元测试的目的是验证单元组件的性能。 单元是软件系统的单个可测试部分,并在应用程序软件的开发阶段进行测试。 此测试旨在测试隔离代码的正确性。单元组件是应用程序的单独功能或代码。白盒测试方法用于单元测试,通常由开发人员完成。 在测试级别层次结构中,单元测试是在集成和其他剩余测试级别之前完成的第一级测试。它使用模块进行测试,减少了等

  • 单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如 C 语言中单元指一个函数,Java 里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进

  • 我们从 Hello World 项目入手,增加单元测试功能。 我们新家了一个名为hello-world-test的 Gradle 项目。 环境 Gradle 3.4.1 Spring Boot 1.5.2.RELEASE Thymeleaf 3.0.3.RELEASE Thymeleaf Layout Dialect 2.2.0 Spring Security Test 4.2.2.RELEASE

  • 单元测试验证单个代码单元是否按预期工作。 也就是说,给定不同的输入参数, 测试验证类方法返回预期结果。 单元测试通常由编写待测试类的人开发。 Yii的单元测试框架 Codeception 基于 PHPUnit,Codeception 建议遵从 PHPUnit 的文档的进行开发: Codeception for Yii framework Codeception Unit Tests PHPUnit