Should I test remoteDataSource implementation? - android

I have an implementation of a remoteDataSource which loooks similar to this:
class MyRemoteDataSource #Inject constructor(private val myApi: myApi) :
RemoteDataSource {
private val someErrorOccurredTryAgain= "Some error occurred. Try again later";
override suspend fun someMethod(url: String): StateFlow<Result<MyClass>> {
val result: StateFlow<Result<MyClass>> = try {
val myClass = myApi.service.someMethod(url).toMyClass()
MutableStateFlow(Result.Success((myClass)))
} catch (socketTimeoutException: SocketTimeoutException) {
MutableStateFlow(Result.Error(Exception("Connectivity issues")))
} catch (httpException: HttpException) {
var exception = Exception(someErrorOccurredTryAgain)
val errorCodeTag = "error_code"
if (httpException.response() != null) {
val errorJson = JSONObject(httpException.response()?.errorBody().toString())
if (errorJson.has(errorCodeTag)) {
val code = errorJson.getInt(errorCodeTag)
exception = when (code) {
2 -> Exception("Some type of error 2")
3 -> Exception("Some type of error 3")
else -> {
Exception("Some error occurred")
}
}
}
}
MutableStateFlow(Result.Error(exception))
} catch (exception: Exception) {
MutableStateFlow(Result.Error(Exception(someErrorOccurredTryAgain)))
}
return result
}
}
And I'm facing three doubts:
1- I'd like to test what happens when receiving a SocketTimeoutException, but when I try to mock an exception being thrown with mockito, I get an error like:
Checked exception is invalid for this method!
Invalid: java.net.SocketTimeoutException
org.mockito.exceptions.base.MockitoException:
Checked exception is invalid for this method!
Invalid: java.net.SocketTimeoutException
is it a good practice to test this kind of things?
2 - Does it makes sense to test some errors that an specific API might return (it's the case of HttpException, where there are some error codes in the errorBody
3 - In order to be able to manage properly exceptions from example from a Repository, is it better to manage the error handling as I did with some class like the following:
sealed class Result<out R> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
override fun toString(): String {
return when (this) {
is Success<*> -> "Success[data=$data]"
is Error -> "Error[exception=$exception]"
}
}
}
val Result<*>.succeeded
get() = this is Result.Success && data != null
or is it better to trhow a custom class of exception with specifics types like ConnectivityError or WrongRequest (based on the API response)

Related

I keep getting the error "E/Network: searchBooks: Failed Getting books" but I am not sure why

So I am using the Google's API and for some reason, I'm getting a generic error:
E/Network: searchBooks: Failed Getting books
When it initially loads up, the hard coded query "android" shows up with a list of books associated with the book topic. But when I search up a different topic like "shoes" for example, the error shows up. Even when you hard code a different topic other than "android", it still shows the error. I have checked the API and it is working properly with the different query searches.
Here's the Retrofit Interface:
#Singleton
interface BooksApi {
#GET(BOOK_EP)
suspend fun getAllBooks(
//don't initialize the query, so that the whole api is available to the user
#Query("q") query: String
): Book
#GET("$BOOK_EP/{bookId}")
suspend fun getBookInfo(
#Path("bookId") bookId: String
): Item
}
The Repo
class BookRepository #Inject constructor(private val api: BooksApi) {
suspend fun getBooks(searchQuery: String): Resource<List<Item>> {
return try {
Resource.Loading(data = true)
val itemList = api.getAllBooks(searchQuery).items
if(itemList.isNotEmpty()) Resource.Loading(data = false)
Resource.Success(data = itemList)
}catch (exception: Exception){
Resource.Error(message = exception.message.toString())
}
}
suspend fun getBookInfo(bookId: String): Resource<Item>{
val response = try {
Resource.Loading(data = true)
api.getBookInfo(bookId)
}catch (exception: Exception){
return Resource.Error(message = "An error occurred ${exception.message.toString()}")
}
Resource.Loading(data = false)
return Resource.Success(data = response)
}
The ViewModel:
class SearchViewModel #Inject constructor(private val repository: BookRepository): ViewModel(){
var list: List<Item> by mutableStateOf(listOf())
var isLoading: Boolean by mutableStateOf(true)
init {
loadBooks()
}
private fun loadBooks() {
searchBooks("android")
}
fun searchBooks(query: String) {
viewModelScope.launch(Dispatchers.Default) {
if (query.isEmpty()){
return#launch
}
try {
when(val response = repository.getBooks(query)){
is Resource.Success -> {
list = response.data!!
if (list.isNotEmpty()) isLoading = false
}
is Resource.Error -> {
isLoading = false
Log.e("Network", "searchBooks: Failed Getting books", )
}
else -> {isLoading = false}
}
}catch (exception: Exception){
isLoading = false
Log.d("Network", "searchBooks: ${exception.message.toString()}")
}
}
}
}
I'll leave the project public so you guys can check it out for more of an understanding
Github Link: https://github.com/OEThe11/ReadersApp
P.S. you would have to create a login (takes 30 sec), but once you do, you'll have access to the app immediately.
This issue is occurring because of JsonSyntaxException java.lang.NumberFormatException while the JSON response is getting parsed from the API. This is because the averageRating field in the VolumeInfo data class is declared as Int but the response can contain floating point values.
If you change averageRating field type from Int to Double in the VolumeInfo data class, the exception would no longer occur.
I suggest you to debug your code in such cases.

Mockito Test: verify static method was called inside object class

// writing junit ExerciseMainLogger class
so how to verify AnalyticsLog.insertEventLog(builder) using Mockito
I have mocked AnalyticsLog class but getting error Actually, there were zero interactions with this mock.
Wanted but not invoked
object ExerciseMainLogger {
fun setLog(eventName: String, screenId: String = "", dimension: Map<String, String> = mapOf()) {
LOG.d(TAG, "setLog - $eventName, $screenId, $dimension")
val builder = LogBuilders.EventBuilder()
.setEventName(eventName)
.setEventType(LogBuilders.EventType.NORMAL)
if (screenId.isNotEmpty()) {
builder.setScreenView(screenId)
}
if (dimension.isNotEmpty()) {
builder.setDimension(dimension)
}
AnalyticsLog.insertEventLog(builder)
}
}
AnalyticsLog Class
object AnalyticsLog {
#JvmStatic
fun insertEventLog(eventBuilder: EventBuilder) {
if (TestConfig.isTestMode()) {
LOG.d(TAG, "[SA] test mode")
return
}
try {
val eventLogs = eventBuilder.build()
val eventId = eventLogs[EVENT_ID_PROPERTY]
val result = insertLog(eventLogs)
if (FeatureManager.getInstance().getBooleanValue(FeatureList.Key.COMMON_DEVELOPER_MODE)) {
LOG.d(TAG, "[SA-DEV] insertEventLog: EventId: $eventId, logs: $eventLogs, send result: $result")
} else {
LOG.d(TAG, "[SA] insertEventLog: EventId: $eventId, send result: $result")
}
} catch (e: Exception) {
LOG.w(TAG, "[SA] insertEventLog exception, " + e.message)
e.printStackTrace()
}
}
}
My Test method looks like below. I trying to run the test case with the JUnit but getting error
mockkStatic(SamsungAnalyticsLog::class)
every { SamsungAnalyticsLog.insertEventLog(builder) } just runs
ExerciseMainLogger.setLog(
ExerciseMainLogger.EX2012,
screenId = screenId,
dimension = dimension
)
verify(exactly = 1) { AnalyticsLog.insertEventLog(builder) }
Verification failed: call 1 of 1: class com.samsung.android.wear.shealth.base.log.SamsungAnalyticsLog.insertEventLog(eq(com.samsung.context.sdk.samsunganalytics.LogBuilders$EventBuilder#71a04ac6))). Only one matching call to SamsungAnalyticsLog(static SamsungAnalyticsLog)/insertEventLog(EventBuilder) happened, but arguments are not matching:
[0]: argument: com.samsung.context.sdk.samsunganalytics.LogBuilders$EventBuilder#7b05129b, matcher: eq(com.samsung.context.sdk.samsunganalytics.LogBuilders$EventBuilder#71a04ac6), result: -
Stack trace:

Type mismatch: inferred type is Unit when Weather was expected

I am working on an Android Weather application and I am getting the error described in the title. The function that is causing this error is a repository that is calling some weather data. I have a helper class called DataOrException which is:
class DataOrException<T, Boolean, E>(
var data: T? = null,
var loading: Kotlin.Boolean? = null,
var e: E? = null
)
The function that is calling this class is a coroutine that is getting the weather information from repository which is using Injection to return the class. Here's the function:
suspend fun getWeather(cityQuery: String, units: String): DataOrException<Weather, Boolean, Exception> {
val response = try {
api.getWeather(query = cityQuery, units = units)
} catch (e: Exception) {
Log.d("REX", "getWeather: $e")
return DataOrException(e = e)
}
return DataOrException(data = response) //Error occurs here.
Any ideas on how to fix this error?
Your getWeather function in WeatherApi is not returning anything so it's basically a kotlin Unit. But, In your repository return DataOrException(data = response) here data is expected to be of type Weather. That's why the error.
Solution:
Return Weather from WeatherApi function getWeather & keep everything else as it was.
interface WeatherApi {
#GET(value = "/cities/cityID=Chelsea")
suspend fun getWeather(#Query("q") query: String, #Query("units") units: String = "imperial") : Weather
}
OR
================
Change data type to Unit by changing to : DataOrException<Unit, Boolean, Exception>
suspend fun getWeather(
cityQuery: String,
units: String
): DataOrException<Unit, Boolean, Exception> {
val response = try {
api.getWeather(query = cityQuery, units = units)
} catch (e: Exception) {
Log.d("REX", "getWeather: $e")
return DataOrException(e = e)
}
return DataOrException(data = response)
}

retrofit - kotlin - Parameter specified as non-null is null

I'm using mvvm , kotlin , retrofit and courtin in my app . I've done several request and all of them works fine but with this one , I get this error "Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter list"
this is my json
{
"roomslist": [
{
"id": "1"
}
]
}
these are my models
data class RoomsListModel(
#Json(name = "roomslist")
val roomsList: List<Rooms>
)
data class Rooms(
#Json(name = "id")
val id: String
}
this is my api interface :
#FormUrlEncoded
#POST("getPlaceRooms.php")
fun getPlaceRooms2(#Field("amlakid")id:String):Deferred<RoomsListModel>
this is my repository :
fun getRooms(
amlakId: String
): MutableLiveData<RoomsListModel> {
scope.launch {
val request = api.getPlaceRooms2(amlakId)
withContext(Dispatchers.Main) {
try {
val response = request.await()
roomsLiveData.value = response
} catch (e: HttpException) {
Log.v("this", e.message);
} catch (e: Throwable) {
Log.v("this", e.message);
}
}
}
return roomsLiveData;
}
when the app runs , it goes into e: Throwable and returns the error
my viewmodel
class PlacesDetailsViewModel : ViewModel() {
private val repository = PlaceDetailsRepository()
fun getRooms(amlakId: String):MutableLiveData<RoomsListModel> {
return repository.getRooms(amlakId)
}
}
and this my activity request :
viewModel.getRooms(amlakId).observe(this, Observer {
vf.frm_loading.visibility = View.GONE
it?.let {
adapter.updateList(it?.roomsList)
setNoItem(false)
}
})
I'm using moshi
I've tried to clean ,rebuild but it doesn't make any different
could you help me ?
what is going wrong with my code?
You should try adding ? to your Model parameters. Not sure if in your case is the String?. It will ensure that you can have null values on your String
val id: String?
Please double check, whatever value is missing or null in your case
Have you tried removing #Json annotation in your val id: String declaration?

How to get the response of another observable when error occurs?

I just want to ask if it is possible to get the response of another observable after encountering an error from the another observable?
for example I am calling a two api Avatar and Attachment using a combineLatest.
val avatar: Observable<ResponseBody> = api().getAvatar()
val attachment: Observable<ResponseBody> = api().getAttachment()
val obs = Observables.combineLatest(avatar, attachment)
.map { it ->
if (it.first is Exception) {
Log.e(TAG, "getAvatar failed")
} else {
updateAvatar()
}
if (it.second is Exception) {
Log.e(TAG, "getAttachment failed")
} else {
updateAttachment()
}
if (it.first !is Exception && it.second !is Exception) {
Log.i(TAG, "success first=${it.first}, second=${it.second}")
updateAll()
}
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.onErrorReturn { it }
.subscribe()
disposable.add(obs)
I just want to get the avatar response if the attachment error and I want to get the attachment response if the avatar error.
Thanks.
Yes, my friend. You can handle error for each observable that you combine by calling onErrorReturn() method. You can use empty ResponseBody for detecting error. Final code
val avatar: Observable<Optional<ResponseBody>> = api().getAvatar().onErrorReturn{ Optional.empty }
val attachment: Observable<Optional<ResponseBody>> = api().getAttachment().onErrorReturn{ Optional.empty }
val obs = Observables.combineLatest(avatar, attachment) {avatar, attachment ->
if (!avatar.isPresent()) {
//logic
}
if (!attachment.isPresent()) {
//logic
}
}.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.onErrorReturn { it }
.subscribe()
If you use java 7 or lower in you project, you can write your own Optional
class Optional<T>(val value: T?) {
companion object {
fun <T> empty(): Optional<T> = Optional(null)
}
fun isPresent() = value != null
}

Categories

Resources