在 android 中使用流协程 kotlin 的单元测试存储库
unit testing repository that use flow coroutine kotlin in android
我想为我的 repository
编写单元测试。 repository
的方法 return flow
。 addDiaryRate
方法首先 emit
加载状态,然后 return 从网络加载数据。我想验证 addDiaryRate
方法发出加载然后数据响应。我写了这个测试,但我的测试 failed.The 错误说预期和实际不一样,但它们是相等的。
java.lang.AssertionError:
Expected: is <DataState(error=null, loading=Loading(isLoading=true), data=null)>
but: was <DataState(error=null, loading=Loading(isLoading=false), data=Event(content=RateResponse(data=RateModel(id=1, symbolPath=symbol path, rate=3, point=2.0, diaryId=333, count=34)), hasBeenHandled=false))>
Expected :is <DataState(error=null, loading=Loading(isLoading=true), data=null)>
Actual :<DataState(error=null, loading=Loading(isLoading=false), data=Event(content=RateResponse(data=RateModel(id=1, symbolPath=symbol path, rate=3, point=2.0, diaryId=333, count=34)), hasBeenHandled=false))>
我使用代码实验室的示例代码和 google 示例。
这是 MainRepository
:
class MainRepositoryImp constructor(
private val apiService: MainApiService,
private val suggestionModelDao: SuggestionModelDao,
private val userDao: UserDao,
private val babyDao: BabyDao,
private val diaryHotDao: DiaryHotDao,
private val diaryUserDao: DiaryUserDao,
private val hashTagModelDao: HashTagModelDao,
private val diaryTopDao: DiaryTopDao,
private val diaryResultSearchDao: DiaryResultSearchDao,
private val rateModelDao: RateModelDao,
private val medalDao: MedalDao,
private val blogModelDao: BlogModelDao,
private val seenDiaryDao: SeenDiaryDao,
private val rateModelAppDao: RateModelAppDao,
private val diarySavedDao: DiarySavedDao,
private val diaryAnotherUserDao: DiaryAnotherUserDao,
private val anotherUserInfoDao: AnotherUserInfoDao,
private val followDao: FollowDao,
private val suggestProductDao: SuggestProductDao,
private val blogSuggestWithHashTagDao: BlogSuggestWithHashTagDao,
private val suggestHashtagDao: SuggestHashtagDao,
private val sessionManager: SessionManager
) : MainRepository {
override fun addRateToDiary(rate: String, diaryId: String): Flow<DataState<RateResponse>> =
flow {
emit(DataState.loading(true))
val apiResult = safeApiCall(
sessionManager.isConnectedToTheInternet()
) {
apiService.addRate(
diaryId,
AddRateRequest(rate),
sessionManager.cachedAccessToken.value ?: ""
)
}
emit(
object : ApiResponseHandler<RateResponse, RateResponse>(
response = apiResult
) {
override suspend fun handleSuccess(resultObj: RateResponse): DataState<RateResponse> {
safeCacheCall {
diaryHotDao.updateRateStateDiary(
resultObj.data.symbolPath ?: "",
diaryId,
resultObj.data.rate
)
var countRate = 0
var userId = 0
userDao.fetchUser()?.let {
countRate = it.countRates + 1
userId = it.id
}
userDao.updateCountRate(countRate, userId)
}
return DataState.data(resultObj)
}
}.getResult()
)
}
}
这是我的测试class:
@ExperimentalCoroutinesApi
@RunWith(RobolectricTestRunner::class)
@InternalCoroutinesApi
class MainRepositoryTest
{
private lateinit var repository: MainRepository
private var apiService = FakeUnitTestApiService()
private lateinit var suggestionModelDao: SuggestionModelDao
private lateinit var userDao: UserDao
private lateinit var babyDao: BabyDao
private lateinit var diaryHotDao: DiaryHotDao
private lateinit var diaryUserDao: DiaryUserDao
private lateinit var hashTagModelDao: HashTagModelDao
private lateinit var diaryTopDao: DiaryTopDao
private lateinit var diaryResultSearchDao: DiaryResultSearchDao
private lateinit var rateModelDao: RateModelDao
private lateinit var medalDao: MedalDao
private lateinit var blogModelDao: BlogModelDao
private lateinit var seenDiaryDao: SeenDiaryDao
private lateinit var rateModelAppDao: RateModelAppDao
private lateinit var diarySavedDao: DiarySavedDao
private lateinit var diaryAnotherUserDao: DiaryAnotherUserDao
private lateinit var anotherUserInfoDao: AnotherUserInfoDao
private lateinit var followDao: FollowDao
private lateinit var suggestProductDao: SuggestProductDao
private lateinit var blogSuggestWithHashTagDao: BlogSuggestWithHashTagDao
private lateinit var suggestHashtagDao: SuggestHashtagDao
private lateinit var sessionManager: SessionManager
private lateinit var db: AppDatabase
private lateinit var prefManager: PrefManager
@Rule
@JvmField
val instantExecutorRule = InstantTaskExecutorRule()
@ExperimentalCoroutinesApi
@get:Rule
var mainCoroutineRule = MainCoroutineRule()
@Before
fun init() {
val app = ApplicationProvider.getApplicationContext<Application>()
prefManager=PrefManager(app)
db = Room.inMemoryDatabaseBuilder(
app,
AppDatabase::class.java
).allowMainThreadQueries().build()
initDao()
sessionManager = SessionManager(
app,
userDao,
babyDao,
diaryHotDao,
diaryTopDao,
diaryUserDao,
hashTagModelDao,
medalDao,
suggestionModelDao,
diaryResultSearchDao,
rateModelDao,
blogModelDao,
seenDiaryDao,
rateModelAppDao,
diarySavedDao,
diaryAnotherUserDao,
anotherUserInfoDao,
followDao,
suggestProductDao,
blogSuggestWithHashTagDao,
suggestHashtagDao,
prefManager
)
repository = MainRepositoryImp(
apiService,
suggestionModelDao,
userDao,
babyDao,
diaryHotDao,
diaryUserDao,
hashTagModelDao,
diaryTopDao,
diaryResultSearchDao,
rateModelDao,
medalDao,
blogModelDao,
seenDiaryDao,
rateModelAppDao,
diarySavedDao,
diaryAnotherUserDao,
anotherUserInfoDao,
followDao,
suggestProductDao,
blogSuggestWithHashTagDao,
suggestHashtagDao,
sessionManager
)
}
private fun initDao() {
suggestionModelDao = db.getSuggestionModelDao()
userDao = db.getUserDao()
babyDao = db.getBabyDao()
diaryHotDao = db.getDiaryHotDao()
diaryUserDao = db.getDiaryUserDao()
hashTagModelDao = db.getHashTagModelDao()
diaryTopDao = db.getDiaryTopDao()
diaryResultSearchDao = db.getDiaryResultSearchDao()
rateModelDao = db.getRateModelDao()
medalDao = db.getMedalDao()
blogModelDao = db.getBlogModelDao()
seenDiaryDao = db.getSeenDiaryDao()
rateModelAppDao = db.getRateModelAppDao()
diarySavedDao = db.getDiarySavedDao()
diaryAnotherUserDao = db.getDiaryAnotherUserDao()
anotherUserInfoDao = db.getAnotherUserInfoDao()
followDao = db.getFollowDao()
suggestProductDao = db.getBlogProductDao()
blogSuggestWithHashTagDao = db.getBlogSuggestWithHashTagDao()
suggestHashtagDao = db.getBlogSuggestDao()
}
@Test
fun addRateTest() = mainCoroutineRule.runBlockingTest{
/** GIVEN **/
val response = RateResponse(
RateModel(
id = "1",
symbolPath = "symbol path",
rate = 3,
point = 2f,
diaryId = "333",
count = "34"
)
)
/** WHEN **/
repository.addRateToDiary("2","333").collect{
assertThat(it, `is`(DataState.loading<RateResponse>(true)))
assertThat(it,`is`(DataState.data<RateResponse>(response)))
}
}
终于找到解决方法了
我使用runBlocking
而不是mainCoroutineRule.runBlockingTest
。
为了验证flow
的所有发射值,我们可以使用toList()
方法。
这是我的 fakeApiService:
open class FakeUnitTestApiService(
var addRateImpl: suspend (
id: String,
addRateRequest: AddRateRequest,
token: String
) -> GenericApiResponse<RateResponse> = notImplemented2()
) : MainApiService {
companion object {
private fun <T, R> notImplemented1(): suspend (t: T) -> R {
return { t: T ->
TODO("")
}
}
private fun <T, R> notImplemented2(): suspend (t: T, s: T, l: T) -> R {
return { t: T, s: T, l: T ->
TODO("")
}
}
}
override suspend fun addRate(
id: String,
addRateRequest: AddRateRequest,
token: String
): GenericApiResponse<RateResponse> = addRateImpl(id,addRateRequest,token)
}
这是我的测试函数:
@Test
fun addRateTest() = runBlocking{
/** GIVEN **/
val response = RateResponse(
RateModel(
id = "1",
symbolPath = "symbol path",
rate = 3,
point = 2f,
diaryId = "333",
count = "34"
)
)
/** WHEN **/
apiService.addRateImpl = { s: String, addRateRequest: AddRateRequest, s1: String ->
GenericApiResponse.create(Response.success(response))
}
val list=repository.addRateToDiary("2","333").toList()
/**THEN**/
assertThat(list.first().loading.isLoading,`is`(true))
assertThat(list.last().data?.peekContent(),`is`(response))
}
我想为我的 repository
编写单元测试。 repository
的方法 return flow
。 addDiaryRate
方法首先 emit
加载状态,然后 return 从网络加载数据。我想验证 addDiaryRate
方法发出加载然后数据响应。我写了这个测试,但我的测试 failed.The 错误说预期和实际不一样,但它们是相等的。
java.lang.AssertionError: Expected: is <DataState(error=null, loading=Loading(isLoading=true), data=null)> but: was <DataState(error=null, loading=Loading(isLoading=false), data=Event(content=RateResponse(data=RateModel(id=1, symbolPath=symbol path, rate=3, point=2.0, diaryId=333, count=34)), hasBeenHandled=false))> Expected :is <DataState(error=null, loading=Loading(isLoading=true), data=null)> Actual :<DataState(error=null, loading=Loading(isLoading=false), data=Event(content=RateResponse(data=RateModel(id=1, symbolPath=symbol path, rate=3, point=2.0, diaryId=333, count=34)), hasBeenHandled=false))>
我使用代码实验室的示例代码和 google 示例。
这是 MainRepository
:
class MainRepositoryImp constructor(
private val apiService: MainApiService,
private val suggestionModelDao: SuggestionModelDao,
private val userDao: UserDao,
private val babyDao: BabyDao,
private val diaryHotDao: DiaryHotDao,
private val diaryUserDao: DiaryUserDao,
private val hashTagModelDao: HashTagModelDao,
private val diaryTopDao: DiaryTopDao,
private val diaryResultSearchDao: DiaryResultSearchDao,
private val rateModelDao: RateModelDao,
private val medalDao: MedalDao,
private val blogModelDao: BlogModelDao,
private val seenDiaryDao: SeenDiaryDao,
private val rateModelAppDao: RateModelAppDao,
private val diarySavedDao: DiarySavedDao,
private val diaryAnotherUserDao: DiaryAnotherUserDao,
private val anotherUserInfoDao: AnotherUserInfoDao,
private val followDao: FollowDao,
private val suggestProductDao: SuggestProductDao,
private val blogSuggestWithHashTagDao: BlogSuggestWithHashTagDao,
private val suggestHashtagDao: SuggestHashtagDao,
private val sessionManager: SessionManager
) : MainRepository {
override fun addRateToDiary(rate: String, diaryId: String): Flow<DataState<RateResponse>> =
flow {
emit(DataState.loading(true))
val apiResult = safeApiCall(
sessionManager.isConnectedToTheInternet()
) {
apiService.addRate(
diaryId,
AddRateRequest(rate),
sessionManager.cachedAccessToken.value ?: ""
)
}
emit(
object : ApiResponseHandler<RateResponse, RateResponse>(
response = apiResult
) {
override suspend fun handleSuccess(resultObj: RateResponse): DataState<RateResponse> {
safeCacheCall {
diaryHotDao.updateRateStateDiary(
resultObj.data.symbolPath ?: "",
diaryId,
resultObj.data.rate
)
var countRate = 0
var userId = 0
userDao.fetchUser()?.let {
countRate = it.countRates + 1
userId = it.id
}
userDao.updateCountRate(countRate, userId)
}
return DataState.data(resultObj)
}
}.getResult()
)
}
}
这是我的测试class:
@ExperimentalCoroutinesApi
@RunWith(RobolectricTestRunner::class)
@InternalCoroutinesApi
class MainRepositoryTest
{
private lateinit var repository: MainRepository
private var apiService = FakeUnitTestApiService()
private lateinit var suggestionModelDao: SuggestionModelDao
private lateinit var userDao: UserDao
private lateinit var babyDao: BabyDao
private lateinit var diaryHotDao: DiaryHotDao
private lateinit var diaryUserDao: DiaryUserDao
private lateinit var hashTagModelDao: HashTagModelDao
private lateinit var diaryTopDao: DiaryTopDao
private lateinit var diaryResultSearchDao: DiaryResultSearchDao
private lateinit var rateModelDao: RateModelDao
private lateinit var medalDao: MedalDao
private lateinit var blogModelDao: BlogModelDao
private lateinit var seenDiaryDao: SeenDiaryDao
private lateinit var rateModelAppDao: RateModelAppDao
private lateinit var diarySavedDao: DiarySavedDao
private lateinit var diaryAnotherUserDao: DiaryAnotherUserDao
private lateinit var anotherUserInfoDao: AnotherUserInfoDao
private lateinit var followDao: FollowDao
private lateinit var suggestProductDao: SuggestProductDao
private lateinit var blogSuggestWithHashTagDao: BlogSuggestWithHashTagDao
private lateinit var suggestHashtagDao: SuggestHashtagDao
private lateinit var sessionManager: SessionManager
private lateinit var db: AppDatabase
private lateinit var prefManager: PrefManager
@Rule
@JvmField
val instantExecutorRule = InstantTaskExecutorRule()
@ExperimentalCoroutinesApi
@get:Rule
var mainCoroutineRule = MainCoroutineRule()
@Before
fun init() {
val app = ApplicationProvider.getApplicationContext<Application>()
prefManager=PrefManager(app)
db = Room.inMemoryDatabaseBuilder(
app,
AppDatabase::class.java
).allowMainThreadQueries().build()
initDao()
sessionManager = SessionManager(
app,
userDao,
babyDao,
diaryHotDao,
diaryTopDao,
diaryUserDao,
hashTagModelDao,
medalDao,
suggestionModelDao,
diaryResultSearchDao,
rateModelDao,
blogModelDao,
seenDiaryDao,
rateModelAppDao,
diarySavedDao,
diaryAnotherUserDao,
anotherUserInfoDao,
followDao,
suggestProductDao,
blogSuggestWithHashTagDao,
suggestHashtagDao,
prefManager
)
repository = MainRepositoryImp(
apiService,
suggestionModelDao,
userDao,
babyDao,
diaryHotDao,
diaryUserDao,
hashTagModelDao,
diaryTopDao,
diaryResultSearchDao,
rateModelDao,
medalDao,
blogModelDao,
seenDiaryDao,
rateModelAppDao,
diarySavedDao,
diaryAnotherUserDao,
anotherUserInfoDao,
followDao,
suggestProductDao,
blogSuggestWithHashTagDao,
suggestHashtagDao,
sessionManager
)
}
private fun initDao() {
suggestionModelDao = db.getSuggestionModelDao()
userDao = db.getUserDao()
babyDao = db.getBabyDao()
diaryHotDao = db.getDiaryHotDao()
diaryUserDao = db.getDiaryUserDao()
hashTagModelDao = db.getHashTagModelDao()
diaryTopDao = db.getDiaryTopDao()
diaryResultSearchDao = db.getDiaryResultSearchDao()
rateModelDao = db.getRateModelDao()
medalDao = db.getMedalDao()
blogModelDao = db.getBlogModelDao()
seenDiaryDao = db.getSeenDiaryDao()
rateModelAppDao = db.getRateModelAppDao()
diarySavedDao = db.getDiarySavedDao()
diaryAnotherUserDao = db.getDiaryAnotherUserDao()
anotherUserInfoDao = db.getAnotherUserInfoDao()
followDao = db.getFollowDao()
suggestProductDao = db.getBlogProductDao()
blogSuggestWithHashTagDao = db.getBlogSuggestWithHashTagDao()
suggestHashtagDao = db.getBlogSuggestDao()
}
@Test
fun addRateTest() = mainCoroutineRule.runBlockingTest{
/** GIVEN **/
val response = RateResponse(
RateModel(
id = "1",
symbolPath = "symbol path",
rate = 3,
point = 2f,
diaryId = "333",
count = "34"
)
)
/** WHEN **/
repository.addRateToDiary("2","333").collect{
assertThat(it, `is`(DataState.loading<RateResponse>(true)))
assertThat(it,`is`(DataState.data<RateResponse>(response)))
}
}
终于找到解决方法了
我使用
runBlocking
而不是mainCoroutineRule.runBlockingTest
。为了验证
flow
的所有发射值,我们可以使用toList()
方法。 这是我的 fakeApiService:open class FakeUnitTestApiService( var addRateImpl: suspend ( id: String, addRateRequest: AddRateRequest, token: String ) -> GenericApiResponse<RateResponse> = notImplemented2() ) : MainApiService { companion object { private fun <T, R> notImplemented1(): suspend (t: T) -> R { return { t: T -> TODO("") } } private fun <T, R> notImplemented2(): suspend (t: T, s: T, l: T) -> R { return { t: T, s: T, l: T -> TODO("") } } } override suspend fun addRate( id: String, addRateRequest: AddRateRequest, token: String ): GenericApiResponse<RateResponse> = addRateImpl(id,addRateRequest,token) }
这是我的测试函数:
@Test
fun addRateTest() = runBlocking{
/** GIVEN **/
val response = RateResponse(
RateModel(
id = "1",
symbolPath = "symbol path",
rate = 3,
point = 2f,
diaryId = "333",
count = "34"
)
)
/** WHEN **/
apiService.addRateImpl = { s: String, addRateRequest: AddRateRequest, s1: String ->
GenericApiResponse.create(Response.success(response))
}
val list=repository.addRateToDiary("2","333").toList()
/**THEN**/
assertThat(list.first().loading.isLoading,`is`(true))
assertThat(list.last().data?.peekContent(),`is`(response))
}