unit testing repository that use flow coroutine kotlin in android

2.9k views Asked by At

I want to write unit test for my repository. The repository's methods return flow. The addDiaryRate method first of all emit loading state then return data from network. I want to verify that addDiaryRate method emit loading then data response. I write this test but my test failed.The error say expected and actual isn't same while they are equal.

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))>

I use sample code of codelab and google samples. This is 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()
            )
        }
}

This is my test 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

There are 1 answers

0
maryam On BEST ANSWER

Finally I find the solution.

  1. I use runBlocking instead of using mainCoroutineRule.runBlockingTest.

  2. For verify all of emitted value of flow,We could use toList() method. This is my 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)
    }
    

And This is my test function:

@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))
}