首页 > 解决方案 > Kotlin Flow:从不收集 emitAll

问题描述

我正在尝试为 kotlin 版本编写一个 UnitTest,networkBoundResource它可以在具有多个功能的多个源中找到

这是我的版本,带有以下问题的标记注释。

inline fun <ResultType, RequestType> networkBoundResource(
    ...
    coroutineDispatcher: CoroutineDispatcher
) = flow {

    emit(Resource.loading(null)) // emit works!

    val data = queryDatabase().firstOrNull()

    val flow = if (shouldFetch(data)) {
        
        emit(Resource.loading(data)) // emit works!

        try {
            saveFetchResult(fetch())
            query().map { Resource.success(it) }
            
        } catch (throwable: Throwable) {
            onFetchFailed(throwable)
            query().map { Resource.error(throwable.toString(), it) }
            
        }
    } else {
        query().map { Resource.success(it) }
    }

    emitAll(flow) // emitAll does not work!

}.catch { exception ->
    emit(Resource.error("An error occurred while fetching data! $exception", null))

}.flowOn(coroutineDispatcher)

这是我对此代码的单元测试之一。对代码进行了一些编辑以专注于我的问题:


@get:Rule
val testCoroutineRule = TestCoroutineRule()

private val coroutineDispatcher = TestCoroutineDispatcher()

@Test
fun networkBoundResource_noCachedData_shouldMakeNetworkCallAndStoreUserInDatabase() = testCoroutineRule.runBlockingTest {
    ...

    // When getAuthToken is called
    val result = networkBoundResource(..., coroutineDispatcher).toList()

    result.forEach {
        println(it)
    }    
}

问题是println(it)只打印Resource.loading(null)排放量。但是如果你看一下flow {}块的最后一行,你会发现应该有另一个val flow. 但是这种排放永远不会到达我的单元测试中。为什么?

标签: androidunit-testingkotlinkotlin-coroutineskotlin-flow

解决方案


我不太确定完整的行为,但本质上你想获得一个资源,并且当前的流量都集中在FlowCollector<T>其中,这使得推理和测试变得更加困难。

我以前从未使用或看过 Google 代码,老实说,我只是看了一眼。我的主要收获是它的封装很差,并且似乎打破了关注点的分离——它管理资源状态,并处理所有 io 工作一类。我希望有 2 个不同的类来分隔该逻辑并允许更轻松的测试。

作为简单的伪代码,我会做这样的事情:

class ResourceRepository {

    suspend fun get(r : Request) : Resource {
        // abstract implementation details network request and io 
        // - this function should only fulfill the request 
        // can now be mocked for testing
        delay(3_000)
        return Resource.success(Any())
    }
}

data class Request(val a : String)

sealed class Resource {

    companion object {
        val loading : Resource get() = Loading
        fun success(a : Any) : Resource = Success(a)
        fun error(t: Throwable) : Resource = Error(t)
    }

    object Loading : Resource()

    data class Success(val a : Any) : Resource()

    data class Error(val t : Throwable) : Resource()
}

fun resourceFromRequest(r : Request) : Flow<Resource> =
    flow { emit(resourceRepository.get(r)) }
        .onStart { emit(Resource.loading) }
        .catch { emit(Resource.error(it)) }

这使您可以大大简化resourceFromRequest()函数的实际测试,因为您只需要模拟存储库和一种方法。这使您可以抽象和处理其他地方的网络和 io 工作,这又可以单独进行测试。


推荐阅读