Monday, 23 August 2021

How to UnitTest kotlin-flow version of networkBoundResource?

I want to UnitTest my AuthRepository which is using this "flow-version" of NetworkBoundResource (find the code for that below)

My current UnitTest is green when writing it like this:

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

But I would actually go deeper and verify the following logic:

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

But the test result tells me in this case that only the first verified call was made (mockUserDao.get()). If you have a look at the logic of networkBoundResource (code below), you will see that the other two calls should also be made.

I also tried to do some fake calls before val data = query().first(). In this case the first fake call was always verified, but everything after that not.

I can give more info at any time, but I think for now this should suffice to start finding out what's going on here...

The SubjectUnderTest is my very simple 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
        )
    }
}

And here the code of the flow-version of networkBoundResource.

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)


from How to UnitTest kotlin-flow version of networkBoundResource?

No comments:

Post a Comment