首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何UnitTest networkBoundResource的kotlin-flow版本?

如何UnitTest networkBoundResource的kotlin-flow版本?
EN

Stack Overflow用户
提问于 2021-08-21 15:02:10
回答 1查看 311关注 0票数 2

我想要UnitTest我的AuthRepository,它正在使用NetworkBoundResource的这个“流版本”(找到下面的代码)

我现在的UnitTest是绿色的,写成这样:

代码语言:javascript
复制
@ExperimentalCoroutinesApi
class AuthRepositoryTest {

    companion object {
        const val FAKE_ID_TOKEN = "FAkE_ID_TOKEN"
    }
    
    @get:Rule
    val testCoroutineRule = TestCoroutineRule()
    
    private val coroutineDispatcher = TestCoroutineDispatcher()
    
    private val mockUserDao = mockk<UserDao>()

    private val mockApiService = mockk<TimetrackerApi>()

    private val sut = AuthRepository(
        mockUserDao, mockApiService, coroutineDispatcher
    )
    
    @Test
    fun getAuthToken_noCachedData_shouldMakeNetworkCall() = testCoroutineRule.runBlockingTest {
        // Given an empty database
        
        // When getAuthToken is called
        sut.getAuthToken(FAKE_ID_TOKEN).first()


        coVerify { 
            // Then first try to fetch data from the DB
            mockUserDao.get()
        }
    }
}

但我实际上会更深入地验证以下逻辑:

代码语言:javascript
复制
coVerifyOrder { 
    // Then first try to fetch data from the DB
    mockUserDao.get()
    
    // Then fetch the User from the API
    mockApiService.getUser(FAKE_ID_TOKEN)
    
    // THen insert the user into the DB
    mockUserDao.insert(any())
}

但是测试结果告诉我,在这种情况下,只进行了第一个验证的调用(mockUserDao.get())。如果你看一下networkBoundResource的逻辑(下面的代码),你会发现另外两个调用也应该被调用。

val data = query().first()之前,我也试过做一些假电话。在这种情况下,第一个假调用总是得到验证,但此后的所有调用都没有得到验证。

我可以随时提供更多信息,但我认为现在应该足以开始了解这里发生了什么……

SubjectUnderTest是我非常简单的AuthRepository

代码语言:javascript
复制
class AuthRepository @Inject constructor(
    private val userDao: UserDao,
    private val apiService: TimetrackerApi,
    private val coroutineDispatcher: CoroutineDispatcher
) {
    
    fun getAuthToken(idToken: String): Flow<Resource<User>> {

        return networkBoundResource(
            query = { userDao.get() },
            fetch = { apiService.getUser(idToken) },
            saveFetchResult = { apiResponse -> 
                if (apiResponse is NetworkResponse.Success) {
                    val user = UserConverter.convert(apiResponse.body)
                    userDao.insert(user)
                }
            },
            coroutineDispatcher = coroutineDispatcher
        )
    }
}

这里是networkBoundResource的flow版本的代码。

代码语言:javascript
复制
inline fun <ResultType, RequestType> networkBoundResource(
    crossinline query: () -> Flow<ResultType>,
    crossinline fetch: suspend () -> RequestType,
    crossinline saveFetchResult: suspend (RequestType) -> Unit,
    crossinline onFetchFailed: (Throwable) -> Unit = { },
    crossinline shouldFetch: (ResultType) -> Boolean = { true },
    coroutineDispatcher: CoroutineDispatcher
) = flow<Resource<ResultType>> {
    
    val data = query().first()

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

        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)
    
}.onStart {
    emit(Resource.loading(null))
    
}.catch {
    emit(Resource.error("An error occurred while fetching data!", null))
    
}.flowOn(coroutineDispatcher)
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-08-26 20:34:23

更新

虽然试图实现进一步的测试,但问题的数量增加了。最后,我对networkBoundResource进行了重写,结果比在上面流传的那个要好得多。相应的问题和答案以及代码可以在这里找到:

Kotlin Flow: emitAll is never collected

原始答案

多亏了@aSemy,我不得不更深入地挖掘。结果是我犯了一系列错误。它们都不是很大,但很难发现。下面是它们:

1.使用协程的断点

调试和设置断点时,应确保将线程挂起设置为"All“。这可以通过右键单击断点,选择单选按钮"All“并单击"Make default”来轻松完成。从那时起,每个断点都将具有正确的设置。

2.正确捕获流异常

在某种程度上,我意识到我已经设置了.catch函数,但我没有从中得到任何异常或任何有用的提示。这是因为我忽略了进入这个函数的异常参数。将代码更改为

代码语言:javascript
复制
.catch { exception ->
    emit(Resource.error("An error occurred while fetching data! $exception", null))
}

是正确调试networkBoundResource所需的重要步骤

3.正确设置UnitTest的“给定”部分

通过启用异常,我得到了userDao.get()没有返回任何内容的信息。然后我突然想到,即使假设数据库是空的,这个调用也应该返回一个空流。因此,我将UnitTest的给定部分更改为

代码语言:javascript
复制
// Given an empty database
every { mockUserDao.get() } returns flowOf()

4.修复生产代码

从这一点开始,我得到了一个有效的UnitTest,这导致我的生产代码中出现了进一步的错误。就像它应该做的那样:)我仍然不是完全的新手,但是在将val data = query().first()设置为firstOrNull()之后,事情变得更好了。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/68874174

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档