• 在 Android 上测试 Kotlin 数据流



    转自:
    https://developer.android.google.cn/kotlin/flow/test?hl=zh-cn#producer

    数据流进行通信的单元或模块的测试方式取决于受测对象使用数据流作为输入还是输出

    • 如果受测对象观察到数据流,您可以在虚构依赖项中生成数据流,而这些可以通过测试进行控制
    • 如果单元或模块公开了数据流,您可以读取并验证测试中的数据流所发出的一个或多个数据项

    一 创建虚构数据提供方

    当受测对象是数据流使用方时,一种常见的测试方法是用虚构实现替换提供方。

    class MyFakeRepository : MyRepository {
        fun observeCount() = flow {
            emit(ITEM_1)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    @Test
    fun myTest() {
        // Given a class with fake dependencies:
        val sut = MyUnitUnderTest(MyFakeRepository())
        // Trigger and verify
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    二 在测试中断言数据流发出

    1、某些测试,您只需要检查来自数据流的第一个发出项或有限数量的项

    @Test
    fun myRepositoryTest() = runTest {
        // Given a repository that combines values from two data sources:
        val repository = MyRepository(fakeSource1, fakeSource2)
    
        // When the repository emits a value
        val firstItem = repository.counter.first() // Returns the first item in the flow
    
        // Then check it's the expected item
        assertEquals(ITEM_1, firstItem)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2、如果该测试需要检查多个值,则调用 toList() 会使数据流等待数据源发出其所有值,然后以列表形式返回这些值

    @Test
    fun myRepositoryTest() = runTest {
        // Given a repository with a fake data source that emits ALL_MESSAGES
        val messages = repository.observeChatMessages().toList()
    
        // When all messages are emitted then they should be ALL_MESSAGES
        assertEquals(ALL_MESSAGES, messages)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3、对于需要更复杂地收集数据项或未返回有限数据项的数据流,您可使用 Flow API 选取并转换数据项。

    // Take the second item
    outputFlow.drop(1).first()
    
    // Take the first 5 items
    outputFlow.take(5).toList()
    
    // Takes the first item verifying that the flow is closed after that
    outputFlow.single()
    
    // Finite data streams
    // Verify that the flow emits exactly N elements (optional predicate)
    outputFlow.count()
    outputFlow.count(predicate)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    测试期间持续收集

    在测试中使用虚构实现时,您可以创建一个收集协程,该协程会持续接收 Repository 中的值

    class Repository(private val dataSource: DataSource) {
        fun scores(): Flow<Int> {
            return dataSource.counts().map { it * 10 }
        }
    }
    
    class FakeDataSource : DataSource {
        private val flow = MutableSharedFlow<Int>()
        suspend fun emit(value: Int) = flow.emit(value)
        override fun counts(): Flow<Int> = flow
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    可以创建一个收集协程,该协程会持续接收 Repository 中的值。

    @Test
    fun continuouslyCollect() = runTest {
        val dataSource = FakeDataSource()
        val repository = Repository(dataSource)
    
        val values = mutableListOf<Int>()
        backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
            repository.scores().toList(values)
        }
    
        dataSource.emit(1)
        assertEquals(10, values[0]) // Assert on the list contents
    
        dataSource.emit(2)
        dataSource.emit(3)
        assertEquals(30, values[2])
    
        assertEquals(3, values.size) // Assert the number of items collected
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    由于此处 Repository 公开的数据流永远无法完成,因此收集它的 toList 调用永远不会返回。
    使用 Turbine 库
    第三方 Turbine 库提供了一个用于创建收集协程的便捷 API,以及用于测试数据流的其他便捷功能

    @Test
    fun usingTurbine() = runTest {
        val dataSource = FakeDataSource()
        val repository = Repository(dataSource)
    
        repository.scores().test {
            // Make calls that will trigger value changes only within test{}
            dataSource.emit(1)
            assertEquals(10, awaitItem())
    
            dataSource.emit(2)
            awaitItem() // Ignore items if needed, can also use skip(n)
    
            dataSource.emit(3)
            assertEquals(30, awaitItem())
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    三 测试 StateFlow

    StateFlow 是一种可观察的数据存储器,可以收集这种存储器来以数据流的形式观察它随时间变化的存储值

    以下 ViewModel 会从 Repository 收集值,并在 StateFlow 中将值提供给界面

    class MyViewModel(private val myRepository: MyRepository) : ViewModel() {
        private val _score = MutableStateFlow(0)
        val score: StateFlow<Int> = _score.asStateFlow()
    
        fun initialize() {
            viewModelScope.launch {
                myRepository.scores().collect { score ->
                    _score.value = score
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    此 Repository 的虚构实现可能如下:

    class FakeRepository : MyRepository {
        private val flow = MutableSharedFlow<Int>()
        suspend fun emit(value: Int) = flow.emit(value)
        override fun scores(): Flow<Int> = flow
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    使用此虚构实现测试 ViewModel 时,您可以从虚构实现发出值,以在 ViewModel 的 StateFlow 中触发更新,然后对更新后的 value 断言:

    @Test
    fun testHotFakeRepository() = runTest {
        val fakeRepository = FakeRepository()
        val viewModel = MyViewModel(fakeRepository)
    
        assertEquals(0, viewModel.score.value) // Assert on the initial value
    
        // Start collecting values from the Repository
        viewModel.initialize()
    
        // Then we can send in values one by one, which the ViewModel will collect
        fakeRepository.emit(1)
        assertEquals(1, viewModel.score.value)
    
        fakeRepository.emit(2)
        fakeRepository.emit(3)
        assertEquals(3, viewModel.score.value) // Assert on the latest value
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    使用 stateIn 创建的 StateFlow

    ViewModel 使用 MutableStateFlow 存储 Repository 中的数据流发出的最新值。这是一种常见的模式,通常通过使用 stateIn 运算符以更简单的方式实现,该运算符会将冷数据流转换为热 StateFlow:

    class MyViewModelWithStateIn(myRepository: MyRepository) : ViewModel() {
        val score: StateFlow<Int> = myRepository.scores()
            .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000L), 0)
    }
    
    • 1
    • 2
    • 3
    • 4
  • 相关阅读:
    Failed at the node-sass@4.13.1 postinstall script.
    高效复用:RecyclerView内部嵌套横向列表时的优化技巧
    word 导出 excel导出
    六、回归与聚类算法 - 模型保存与加载
    foolgo解析—ChainSet类笔记
    nvm切换node后,没有npm
    flink学习之sql-client之踩坑记录
    产品代码都给你看了,可别再说不会DDD(九):领域事件
    【前端面试必知】虚拟DOM与diff算法详解
    雷顿学院大数据(一期课程)
  • 原文地址:https://blog.csdn.net/baopengjian/article/details/134054458