在 android 中使用流协程 kotlin 的单元测试存储库

unit testing repository that use flow coroutine kotlin in android

我想为我的 repository 编写单元测试。 repository 的方法 return flowaddDiaryRate 方法首先 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)))
        }
    }

终于找到解决方法了

  1. 我使用runBlocking而不是mainCoroutineRule.runBlockingTest

  2. 为了验证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))
}