我想要UnitTest我的AuthRepository,它正在使用NetworkBoundResource的这个“流版本”(找到下面的代码)
我现在的UnitTest是绿色的,写成这样:
@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()
}
}
}但我实际上会更深入地验证以下逻辑:
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
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版本的代码。
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)发布于 2021-08-26 20:34:23
更新
虽然试图实现进一步的测试,但问题的数量增加了。最后,我对networkBoundResource进行了重写,结果比在上面流传的那个要好得多。相应的问题和答案以及代码可以在这里找到:
Kotlin Flow: emitAll is never collected
原始答案
多亏了@aSemy,我不得不更深入地挖掘。结果是我犯了一系列错误。它们都不是很大,但很难发现。下面是它们:
1.使用协程的断点
调试和设置断点时,应确保将线程挂起设置为"All“。这可以通过右键单击断点,选择单选按钮"All“并单击"Make default”来轻松完成。从那时起,每个断点都将具有正确的设置。
2.正确捕获流异常
在某种程度上,我意识到我已经设置了.catch函数,但我没有从中得到任何异常或任何有用的提示。这是因为我忽略了进入这个函数的异常参数。将代码更改为
.catch { exception ->
emit(Resource.error("An error occurred while fetching data! $exception", null))
}是正确调试networkBoundResource所需的重要步骤
3.正确设置UnitTest的“给定”部分
通过启用异常,我得到了userDao.get()没有返回任何内容的信息。然后我突然想到,即使假设数据库是空的,这个调用也应该返回一个空流。因此,我将UnitTest的给定部分更改为
// Given an empty database
every { mockUserDao.get() } returns flowOf()4.修复生产代码
从这一点开始,我得到了一个有效的UnitTest,这导致我的生产代码中出现了进一步的错误。就像它应该做的那样:)我仍然不是完全的新手,但是在将val data = query().first()设置为firstOrNull()之后,事情变得更好了。
https://stackoverflow.com/questions/68874174
复制相似问题