Problems with Kotlin Result<T> on unit tests - android

I am working on an android Application and I opted to use Kotlin Result class so as to handle success/failure on my operations. I made the changes to the code, but the tests stop working and I cannot understand why. Here I show you some snippets:
FireStoreClient.kt
suspend fun items(): Result<ItemsResponse>
NetworkDataSource.kt
suspend fun getItems(): List<Item> =
fireStoreClient.items().fold({ it.items.map { item -> item.toDomain() } }, { emptyList() })
NetworkDataSourceTest.kt
#ExperimentalCoroutinesApi
#Test
fun `Check getItems works properly`() = runBlockingTest {
whenever(fireStoreClient.items()).doReturn(success(MOCK_ITEMS_DOCUMENT))
val expectedResult = listOf(
Item(
id = 1,
desc = "Description 1"
),
Item(
id = 2,
desc = "Description 2"
)
)
assertEquals(expectedResult, dataSource.getItems())
}
And this is the exception I am getting right now. Any clue? It appears that the fold() method is not being executed when unit testing.
java.lang.ClassCastException: kotlin.Result cannot be cast to ItemsResponse
at NetworkDataSource.getItems(NetworkDataSource.kt:31)

I've found a different workaround for this result-wrapping issue, for those who don't want to make their own Result type.
This issue appears to happens specifically when using Mockito's .thenReturn on suspend functions. I've found that using .thenAnswer doesn't exhibit the problem.
So instead of writing this in your unit test (changed doReturn to thenReturn here):
whenever(fireStoreClient.items()).thenReturn(success(MOCK_ITEMS_DOCUMENT))
Use:
whenever(fireStoreClient.items()).thenAnswer { success(MOCK_ITEMS_DOCUMENT) }
Edit: I should note that I was still experiencing this issue when running Kotlin 1.5.0.
Edit: On Kotlin 1.5.20 I can use .thenReturn again.

After a deep dive into the problem, finally, I've found a temporary workaround that works in the testing environment. The problem is, somehow the value of the Result object is wrapped by another Result, and we can pull the desired value or exception using reflection.
So, I've created an extension function called mockSafeFold, which implements the fold behavior in normal calls, and acts fine when you are executing unit-tests.
inline fun <R, reified T> Result<T>.mockSafeFold(
onSuccess: (value: T) -> R,
onFailure: (exception: Throwable) -> R
): R = when {
isSuccess -> {
val value = getOrNull()
try {
onSuccess(value as T)
} catch (e: ClassCastException) {
// This block of code is only executed in testing environment, when we are mocking a
// function that returns a `Result` object.
val valueNotNull = value!!
if ((value as Result<*>).isSuccess) {
valueNotNull::class.java.getDeclaredField("value").let {
it.isAccessible = true
it.get(value) as T
}.let(onSuccess)
} else {
valueNotNull::class.java.getDeclaredField("value").let {
it.isAccessible = true
it.get(value)
}.let { failure ->
failure!!::class.java.getDeclaredField("exception").let {
it.isAccessible = true
it.get(failure) as Exception
}
}.let(onFailure)
}
}
}
else -> onFailure(exceptionOrNull() ?: Exception())
}
Then, simply call it instead of fold:
val result: Result = myUseCase(param)
result.mockSafeFold(
onSuccess = { /* do whatever */ },
onFailure = { /* do whatever */ }
)

I had the same issue.
I noticed that my method of injected class which should return Result<List<Any>> returns actually Result<Result<List<Any>>> which causes the ClassCastException. I used the Evaluate Expression option for the result from the method and I got
Success(Success([]))
The app works well but unit tests didn't pass due this problem.
As a temporary solution I built a new simple implementation of Result sealed class with fold() extension function. It should be easy to replace in future to kotlin.Result
Result sealed class:
sealed class Result<T> {
data class Success<T>(val value: T) : Result<T>()
data class Failure<T>(val error: Throwable) : Result<T>()
}
fold() extension function:
inline fun <R, T> Result<T>.fold(
onSuccess: (value: T) -> R,
onFailure: (exception: Throwable) -> R
): R = when (this) {
is Result.Success -> onSuccess(value)
is Result.Failure -> onFailure(error)
}

Related

Kotlin Flow: emitAll is never collected

I am trying to write a UnitTest for the kotlin-version of networkBoundResource that can be found on serveral sources with several features
Here is my version of it with marker-comments for the following question.
inline fun <ResultType, RequestType> networkBoundResource(
...
coroutineDispatcher: CoroutineDispatcher
) = flow {
emit(Resource.loading(null)) // emit works!
val data = queryDatabase().firstOrNull()
val flow = if (shouldFetch(data)) {
emit(Resource.loading(data)) // emit works!
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) // emitAll does not work!
}.catch { exception ->
emit(Resource.error("An error occurred while fetching data! $exception", null))
}.flowOn(coroutineDispatcher)
This is one of my UnitTests for this code. The code is edited a bit to focus on my question:
#get:Rule
val testCoroutineRule = TestCoroutineRule()
private val coroutineDispatcher = TestCoroutineDispatcher()
#Test
fun networkBoundResource_noCachedData_shouldMakeNetworkCallAndStoreUserInDatabase() = testCoroutineRule.runBlockingTest {
...
// When getAuthToken is called
val result = networkBoundResource(..., coroutineDispatcher).toList()
result.forEach {
println(it)
}
}
The problem is that println(it) is only printing the Resource.loading(null) emissions. But if you have a look at the last line of the flow {} block, you will see that there should be another emission of the val flow. But this emission never arrives in my UnitTest. Why?
I'm not too sure of the complete behaviour, but essentially you want to get a resource, and current flow is all lumped into the FlowCollector<T> which makes it harder to reason and test.
I have never used or seen the Google code before and if I'm honest only glanced at it. My main take away was it had poor encapsulation and seems to break separations of concern - it manages the resource state, and handles all io work one one class. I'd prefer to have 2 different classes to separate that logic and allows for easier testing.
As simple pseudo code I would do something like this :
class ResourceRepository {
suspend fun get(r : Request) : Resource {
// abstract implementation details network request and io
// - this function should only fulfill the request
// can now be mocked for testing
delay(3_000)
return Resource.success(Any())
}
}
data class Request(val a : String)
sealed class Resource {
companion object {
val loading : Resource get() = Loading
fun success(a : Any) : Resource = Success(a)
fun error(t: Throwable) : Resource = Error(t)
}
object Loading : Resource()
data class Success(val a : Any) : Resource()
data class Error(val t : Throwable) : Resource()
}
fun resourceFromRequest(r : Request) : Flow<Resource> =
flow { emit(resourceRepository.get(r)) }
.onStart { emit(Resource.loading) }
.catch { emit(Resource.error(it)) }
This allows you to massively simplify the actual testing of the resourceFromRequest() function as you only have to mock the repository and one method. This allows you to abstract and deal with the networking and io work elsewhere, independently which again can be tested in isolation.
As #MarkKeen suggested, I now created my own implementation and it works quite well. Compared to the code that is going around on SO, this version now injects the coroutineDispatcher for easier testing, it lets flow take care of error handling, it does not contain nested flows and is imho easier to read and understand, too. There is still the side-effect of storing updated data to the database, but I am too tired now to tackle this.
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.*
inline fun <ResultType, RequestType> networkBoundResource(
crossinline query: () -> Flow<ResultType?>,
crossinline fetch: suspend () -> RequestType,
crossinline saveFetchResult: suspend (RequestType) -> Unit,
crossinline shouldFetch: (ResultType?) -> Boolean = { true },
coroutineDispatcher: CoroutineDispatcher
) = flow<Resource<ResultType>> {
// check for data in database
val data = query().firstOrNull()
if (data != null) {
// data is not null -> update loading status
emit(Resource.loading(data))
}
if (shouldFetch(data)) {
// Need to fetch data -> call backend
val fetchResult = fetch()
// got data from backend, store it in database
saveFetchResult(fetchResult)
}
// load updated data from database (must not return null anymore)
val updatedData = query().first()
// emit updated data
emit(Resource.success(updatedData))
}.onStart {
emit(Resource.loading(null))
}.catch { exception ->
emit(Resource.error("An error occurred while fetching data! $exception", null))
}.flowOn(coroutineDispatcher)
One possible UnitTest for this inline fun, which is used in an AuthRepsitory:
#ExperimentalCoroutinesApi
class AuthRepositoryTest {
companion object {
const val FAKE_ID_TOKEN = "FAkE_ID_TOKEN"
}
#get:Rule
val testCoroutineRule = TestCoroutineRule()
private val coroutineDispatcher = TestCoroutineDispatcher()
private val userDaoFake = spyk<UserDaoFake>()
private val mockApiService = mockk<MyApi>()
private val sut = AuthRepository(
userDaoFake, mockApiService, coroutineDispatcher
)
#Before
fun beforeEachTest() {
userDaoFake.clear()
}
#Test
fun getAuthToken_noCachedData_shouldMakeNetworkCallAndStoreUserInDatabase() = testCoroutineRule.runBlockingTest {
// Given an empty database
coEvery { mockApiService.getUser(any()) } returns NetworkResponse.Success(UserFakes.getNetworkUser(), null, HttpURLConnection.HTTP_OK)
// When getAuthToken is called
val result = sut.getAuthToken(FAKE_ID_TOKEN).toList()
coVerifyOrder {
// Then first try to fetch data from the DB
userDaoFake.get()
// Then fetch the User from the API
mockApiService.getUser(FAKE_ID_TOKEN)
// Then insert the user into the DB
userDaoFake.insert(any())
// Finally return the inserted user from the DB
userDaoFake.get()
}
assertThat(result).containsExactly(
Resource.loading(null),
Resource.success(UserFakes.getAppUser())
).inOrder()
}
}

How to handle database call errors using Flows

Usually I'm returning from my dao suspend function:
#Dao
interface DataDao {
#Query("SELECT * FROM data")
fun getAllData(): List<Data>
}
And handle the call within the repository:
class DataRepository(
private val dataDao: DataDao
) {
fun getAllData(): Flow<DataState> = flow {
val cacheResult = safeDatabaseCall(dispatcher = Dispatchers.IO) { dataDao.getAllData() }
//handle cacheResult, convert to DataState, emit DataState values
}.flowOn(Dispatchers.IO)
}
With generic fun:
suspend fun <T> safeDatabaseCall(
dispatcher: CoroutineDispatcher,
cacheCall: suspend () -> T?
): CacheResult<T?> {
return withContext(dispatcher) {
try {
withTimeout(10000L) {
CacheResult.Success(cacheCall.invoke())
}
} catch (t: Throwable) {
when (t) {
is TimeoutCancellationException -> {
CacheResult.Error("Timeout error")
}
else -> {
CacheResult.Error("Unknown error")
}
}
}
}
}
The problem is that I want return fun getAllData(): Flow<List<Data>> instead of fun getAllData(): List<Data> In order to get immediate updates, But if I'm returning Flow from the Dao, I can't handle the call with safe call and catch errors.
I thought about collecting the data, but if i'm collecting the data the call already done without error handling
Basically I need the cache result return CacheResult<Data> and not CacheResult<Flow<Data>>
How can I solve the problem And make a generic safeDatabaseCall while returning Flow from Dao?
So if I understand correctly you just want to handle the query and return of information safely in a flow. My only question is around the types. I can sorta assume Data DataState and CacheResult are not the same types so I use a "magic" function that converts the intermediary values to the correct one. You will need to adjust accordingly
class DataRepository(
private val dataDao: DataDao
) {
fun getAllData(): Flow<DataState> = flow {
val result = safeDatabaseCall(dispatcher = Dispatchers.IO) {
dataDao.getAllData()
}
// Emit the result
emit(result)
}.catch { t : Throwable ->
// Do our transformation like before
val result = when (t) {
is TimeoutCancellationException -> {
CacheResult.Error("Timeout error")
}
else -> {
CacheResult.Error("Unknown error")
}
}
// And because catch is actually extending a FlowCollector
// We can emit the result in the stream
emit(result)
}.map { cacheResult ->
convertToDataOrDataState(cacheResult)
}
You shouldn't need flowOn with a dispatcher here since the work inside this flow doesn't require thread dispatching
to Dispatcher.IO. The code we are putting in our flow, is purely exception handling and invoking a function. The only place that seems to require any manual dispatch changing is, safeDatabaseCall(). I am not familiar with this function but if it does exist and takes a dispatcher for the result of actualing making the db calls on an IO thread, then all should be good without flowOn. Otherwise you will be switching dispatchers from original dispatcher -> IO and then to IO again. It's not much but the extra no-op context switch doesn't add anything other than confusion later on.
The flow itself traps any upstream issues and you then make them part of the resulting flow

Abstract coroutines structure

With RxJava we can do something like this:
BaseViewModel
protected void subscribe(Completable completable, MutableLiveData<Response> response) {
mDisposable.add(
completable.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.doOnSubscribe(disposable -> response.setValue(Response.loading()))
.doFinally(() -> response.setValue(Response.idle()))
.subscribe(
() -> response.setValue(Response.success(true)),
e -> response.setValue(Response.error(e))
)
);
}
protected <T> void subscribe(Single<T> single, MutableLiveData<Response> response) {
mDisposable.add(
single.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.doOnSubscribe(disposable -> response.setValue(Response.loading()))
.doFinally(() -> response.setValue(Response.idle()))
.subscribe(
result -> response.setValue(Response.success(result)),
e -> response.setValue(Response.error(e))
)
);
}
Then, from repository we getting Single/Complete and pass it to our custom subscribe(), then we get generic Result with data(optional), very easy way to work with asynchronous requests.
How we can abstract coroutines with similar structure, instead of write Launch in every method in ViewModel and try/catch error manually?
Instead of closely following the code you already have with minimal adaptations, I suggest you review your design altogether when migrating to coroutines.
One important principle embedded into coroutines is structured concurrency. This isn't just about the coroutine scopes and cancellation, it is also about the use of futures by any name (be it CompletionStage, Deferred, Task, Single or any other). According to structured concurrency, a future is basically equivalent to a live thread that has no defined scope. You should avoid them.
Instead you should have clearly delineated places in the code that launch new concurrent work contained within a single top-level block of code provided at the launch site.
So far, that implies that you do have a launch block at each entry point into your code from the Android framework, and that's a lot of places due to the nature of the callback-oriented programming model.
However, everything within that block should be coded according to structured concurrency. If you have just one network call to make, your code is entirely sequential: make the call, get the response, process it. The network calls themselves become suspend functions that complete with the result of the call and do not accept callbacks. All the traditional design patterns from the world of blocking calls apply here.
See here for an intro to using coroutines with LiveData, it may help you map your design to the coroutine-oriented one:
https://developer.android.com/topic/libraries/architecture/coroutines#livedata
You are probably looking for something like this
CoroutineWrapper
fun <T> ViewModel.apiCx(context: CoroutineContext = Dispatchers.Default, init: suspend CxWrapper<T>.() -> Unit) {
val wrap = CxWrapper<T>(context)
wrap.launch {
try {
init.invoke(wrap)
callCx(wrap)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
private fun <T> callCx(wrap: CxWrapper<T>) {
val response: Response<T>? = wrap.request
response?.let {
if (it.isSuccessful) {
wrap.success(it.body())
} else {
wrap.fail(Pair(it.code(), it.message()))
}
}
}
class CxWrapper<T>(override val coroutineContext: CoroutineContext) : CoroutineScope {
var request: Response<T>? = null
internal var success: (T?) -> Unit = {}
internal var fail: (Pair<Int, String?>) -> Unit = {}
fun success(onSuccess: (T?) -> Unit) {
success = onSuccess
}
fun error(onError: (Pair<Int, String?>) -> Unit) {
fail = onError
}
}
you can have this as a separate helper class and to use this from your ViewModel
apiCx<YourModelClass> {
request = yourApiCall()
success { yourModelClass ->
Log.d(TAG, "success")
}
error {
Log.e(TAG, "error")
}
}
You would just do the same, just adapted to coroutines. Just replace the different stream types with the suspension methods you need.
protected inline fun <T> MutableLiveData<Response>.subscribe(single: suspend () -> T) {
viewModelScope.launch {
try {
value = Response.loading()
value = withContext(Dispatchers.IO) {
Response.success(single())
}
} catch(e: Throwable) {
value = Response.error(e)
} finally {
value = Response.idle()
}
}
To use it just call with the livedata as receiver
responseLiveData.subscribe<T> {
singleFromRepo()
}
responseLiveData.subscribe<Unit> {
completableFromRepo()
}

Making synchronous calls to Cloud Firestore when running off the main thread

I am building an app based off of the Android Clean Architecture Kotlin version (https://github.com/android10/Android-CleanArchitecture-Kotlin).
Using this architecture, each time you want to invoke a use case, a Kotlin coroutine is launched and the result is posted in the main thread. This is achieved by this code:
abstract class UseCase<out Type, in Params> where Type : Any {
abstract suspend fun run(params: Params): Either<Failure, Type>
fun execute(onResult: (Either<Failure, Type>) -> Unit, params: Params) {
val job = async(CommonPool) { run(params) }
launch(UI) { onResult.invoke(job.await()) }
}
In his example architecture, Mr. Android10 uses Retrofit to make a synchronous api call inside the kotlin couroutine. For example:
override fun movies(): Either<Failure, List<Movie>> {
return when (networkHandler.isConnected) {
true -> request(service.movies(), { it.map { it.toMovie() } }, emptyList())
false, null -> Left(NetworkConnection())
}
}
private fun <T, R> request(call: Call<T>, transform: (T) -> R, default: T): Either<Failure, R> {
return try {
val response = call.execute()
when (response.isSuccessful) {
true -> Right(transform((response.body() ?: default)))
false -> Left(ServerError())
}
} catch (exception: Throwable) {
Left(ServerError())
}
}
'Either' represents a disjoint type, meaning the result will either be a Failure or the object of type T you want.
His service.movies() method is implemented like so (using retrofit)
#GET(MOVIES) fun movies(): Call<List<MovieEntity>>
Now here is my question. I am replacing retrofit with Google Cloud Firestore. I know that currently, Firebase/Firestore is an all async library. I want to know if anyone knows of a method more elegant way of making a synchronous API call to Firebase.
I implemented my own version of Call:
interface Call<T: Any> {
fun execute(): Response<T>
data class Response<T>(var isSuccessful: Boolean, var body: T?, var failure: Failure?)
}
and my API call is implemented here
override fun movieList(): Call<List<MovieEntity>> = object : Call<List<MovieEntity>> {
override fun execute(): Call.Response<List<MovieEntity>> {
return movieListResponse()
}
}
private fun movieListResponse(): Call.Response<List<MovieEntity>> {
var response: Call.Response<List<MovieEntity>>? = null
FirebaseFirestore.getInstance().collection(DataConfig.databasePath + MOVIES_PATH).get().addOnCompleteListener { task ->
response = when {
!task.isSuccessful -> Call.Response(false, null, Failure.ServerError())
task.result.isEmpty -> Call.Response(false, null, MovieFailure.ListNotAvailable())
else -> Call.Response(true, task.result.mapTo(ArrayList()) { MovieEntity.fromSnapshot(it) }, null)
}
}
while (response == null)
Thread.sleep(50)
return response as Call.Response<List<MovieEntity>>
}
Of course, the while loop at the end bothers me. Is there any other, more elegant ways, to wait for the response to be assigned before returning from the movieListResponse method?
I tried calling await() on the Task that is returned from the Firebase get() method, but the movieListResponse method would return immediately anyway. Thanks for the help!
So I found what I was looking for in the Google Tasks API: "If your program is already executing in a background thread you can block a task to get the result synchronously and avoid callbacks" https://developers.google.com/android/guides/tasks#blocking
So my previous problematic code becomes:
private fun movieListResponse(): Call.Response<List<MovieEntity>> {
return try {
val taskResult = Tasks.await(FirebaseFirestore.getInstance().
collection(DataConfig.databasePath + MOVIES_PATH).get(), 2, TimeUnit.SECONDS)
Call.Response(true, taskResult.mapTo(ArrayList()) { MovieEntity.fromSnapshot(it) }, null)
} catch (e: ExecutionException) {
Call.Response(false, null, Failure.ServerError())
} catch (e: InterruptedException) {
Call.Response(false, null, Failure.InterruptedError())
} catch (e: TimeoutException) {
Call.Response(false, null, Failure.TimeoutError())
}
}
Note I no longer need my Thread.sleep while loop.
This code should only be run in a background thread/kotlin coroutine.
This is overengineered, there are several layers trying to do the same thing. I suggest you go back a few steps, undo the abstractions and get into the mood of using coroutines directly. Implement a suspend fun according to this template. You don't need the crutches of Either, handle exceptions in the most natural way: a try-catch around a suspend fun call.
You should end up with a signature as follows:
suspend fun movieList(): List<MovieEntity>
Call site:
launch(UI) {
try {
val list = movieList()
...
} catch (e: FireException) {
// handle
}
}
That's is not the way how firebase works. Firebase is based on callback.
I recommend architecture component's livedata.
Please check the following example.
here is a link: https://android.jlelse.eu/android-architecture-components-with-firebase-907b7699f6a0

Best way to pass multiple lambda functions in Kotlin to handle success/failure

New at Kotlin here and trying to learn the best way to use the higher order functions and passing lambdas. I've created this method to call an API and return an object created from a string OR return a failure if something went wrong.
fun getDeviceStatus(onSuccess: (Device) -> Unit, onFailure: ((String) -> Unit)? = null) {
FuelClient.get(DEVICE_URL,
success = { responseString ->
val adapter = MoshiUtil.moshi.adapter(Device::class.java)
val deivce= adapter.fromJson(responseString)!!
onSuccess(device)
},
failure = { onFailure?.invoke(it.message!!)})
}
I can use this function fine like so:
DeviceService.getDeviceStatus(
{ w ->
print("device")
},
{ e -> print(e) })
But it bothers me a bit that I can't see the name of the functions to see what each function does. I"m wondering if there is a cleaner/better way to do this, like
DeviceService.getDeviceStatus(){
onSuccess{print("device")}
onFailure{print("error")}
}
or maybe
DeviceService.getDeviceStatus()
.onSuccess{print("device")}
.onFailure{print("error")}
But those gives errors. Any thoughts on how to best handle the onSuccess/onFailure use case that is very common? Thx
You can attach a name to each variable in kotlin. Change your code like this
DeviceService.getDeviceStatus(
onSuccess = { w ->
print("device")
},
onFailure = { e -> print(e) })
For this specific case, when the second lambda is optional, infix functions work very well:
sealed class DeviceStatusResult {
abstract infix fun onFailure(handler: (String) -> Unit)
}
class DeviceStatusSuccess(val device: Device) : DeviceStatusResult() {
override fun onFailure(handler: (String) -> Unit) = Unit
}
class DeviceStatusFailure(val errorMessage: String) : DeviceStatusResult() {
override fun onFailure(handler: (String) -> Unit) = handler(errorMessage)
}
fun getDeviceStatus(onSuccess: (Device) -> Unit): DeviceStatusResult {
// get device status
// if (success)
val device = Device()
onSuccess(device)
return DeviceStatusSuccess(device)
// else
// return DeviceStatusFailure(message)
}
Then it can used like
getDeviceStatus { device ->
println(device)
} onFailure { errorMessage ->
System.err.println(errorMessage)
}
Maybe onFailure should be called orFail or something like that.
It is good when the second argument is optional, but not so much otherwise because it doesn't force the user to actually supply a failure handler. And I don't think it's a good idea because it will be too easy to accidentally omit a failure handler. It's much better to force the user to provide one, even if it happens to be an empty one. Therefore, it is better to use named arguments for this case, even though nothing forces to actually name them.
For example we have a class which needs to have more than one function such as two functions as parameter:
class TestClass internal constructor(
private val onClickShowName: (String) -> Unit,
private val onClickShowSurname: (String) -> Unit
) { //Your work. }
Then you need to create val as TestClass:
class MainActivity {
val mTestClass = TestClass(
onClickShowName = {dataText: String -> Log.i("TEST", dataText)},
onClickShowSurname = {dataText: String -> Log.i("TEST", dataText)}
)
}

Categories

Resources