Mockito Test: verify static method was called inside object class - android

// 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:

Related

Should I test remoteDataSource implementation?

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)

Make return statement wait until bg work finishes

This method is intended to get data from server and return them.
requestGroups() method in my code calls execute() method of third party library. execute() launches background executor that adds data to my list, but it returns empty list, because data adding to list occurs in the background and return happens immediately after executor lauching. How can I make return statement wait until all data are added to the list?
RepositoryImpl.kt:
class RepositoryImpl : Repository {
override fun requestGroups(): List<VKGroup> {
val vkGroups = mutableListOf<VKGroup>()
VK.execute(GroupsService().groupsGetExtended(), object : // execute launches executor...
VKApiCallback<GroupsGetObjectExtendedResponse> {
override fun success(result: GroupsGetObjectExtendedResponse) {
val groups = result.items
Log.d(TAG, "result groups in repo: ${groups}")
if (groups.isNotEmpty()) {
groups.forEach { group ->
vkGroups.add(
VKGroup(
id = group.id.value,
name = group.name ?: "",
photo = group.photo200 ?: ""
)
)
}
}
Log.d(TAG, "vkGroups in repo: ${vkGroups}")
}
override fun fail(error: Exception) {
Log.e(TAG, error.toString())
}
})
Log.d(TAG, "vkGroups in repo before return: ${vkGroups}")
return vkGroups //and immediately returns empty list
}
}
Third party execute function:
/**
* Execute api request with callback
* You can use this method in UI thread
* Also you can use your own async mechanism, like coroutines or RX
*/
#JvmStatic
fun <T>execute(request: ApiCommand<T>, callback: VKApiCallback<T>? = null) {
VKScheduler.networkExecutor.submit {
try {
val result = executeSync(request)
VKScheduler.runOnMainThread(Runnable {
callback?.success(result)
})
} catch (e: Exception) {
VKScheduler.runOnMainThread(Runnable {
if (e is VKApiExecutionException && e.isInvalidCredentialsError) {
handleTokenExpired()
}
callback?.fail(e)
})
}
}
}

How to return to launch inside of the collect?

I have a method that looks like that:
private lateinit var cards: List<Card>
fun start() = viewModelScope.launch {
if (!::cards.isInitialized) {
getCards().collect { result ->
result
.doIfSuccess {
cards = it.data
Log.d(TAG, "Received cards")
}
.doIfError {
_errorState.setIfNotEqual(it.exception)
Log.e(TAG, "Cards were not received because of ${it.exception}")
return#collect // <--- that's the place
}
}
}
Log.d(TAG, "Message that needs to be shown only if cards were received")
if (сards.isEmpty()) {
Log.e(TAG, "Сards list is empty")
_errorState.setIfNotEqual(NoCardsException)
return#launch
}
val сard = сards[0]
}
I need to completely return from the method, not only from the .collect block, I've tried to use return#launch or some other custom labels, but it doesn't work even though Kotlin compiler suggests me to set it like that:
I think you can use transformWhile to create a new Flow that does an operation on each item you receive until you return false. Then collect that Flow. I didn't test this because I'm not really sure of how you've structured .doIfSuccess and .doIfError.
fun start() = viewModelScope.launch {
if (!::cards.isInitialized) {
getCards().transformWhile { result ->
result
.doIfSuccess {
cards = it.data
Log.d(TAG, "Received cards")
}
.doIfError {
_errorState.setIfNotEqual(it.exception)
Log.e(TAG, "Cards were not received because of ${it.exception}")
return#transformWhile false
}
return#transformWhile true
}.collect()
}
//...
}
EDIT:
If you only want the first value from the Flow, you could do this:
fun start() = viewModelScope.launch {
if (!::cards.isInitialized) {
getCards().first()
.doIfSuccess {
cards = it.data
Log.d(TAG, "Received cards")
}
.doIfError {
_errorState.setIfNotEqual(it.exception)
Log.e(TAG, "Cards were not received because of ${it.exception}")
return#launch
}
}
//...
}

Callback transformed to suspend function loops infinitely when runBlocking

I am rewriting a java class to kotlin replacing callback with a suspend function. This is my java code:
#IgnoreExtraProperties
public class DeviceType {
public String manufacturer;
public String marketName;
public String model;
public DeviceType(String manufacturer, String marketName, String model) {
this.manufacturer = manufacturer;
this.marketName = marketName;
this.model = model;
}
public DeviceType(){}
public DeviceType(Context context) {
DeviceName.with(context).request(new DeviceName.Callback() {
#Override
public void onFinished(DeviceName.DeviceInfo info, Exception error) {
if (error == null) {
manufacturer = info.manufacturer;
marketName = info.marketName;
model = info.model;
} else
Log.e("DeviceType: ", error.getMessage());
}
});
}
#Override
public String toString() {
if (model == null) {
return "No device type recognized!";
} else {
if (marketName.equals(model))
return manufacturer + " " +marketName;
else
return manufacturer + " " +marketName+ " (" +model+ ")";
}
}
DeviceName class belongs to library AndroidDeviceNames.
Below is my new code in Kotlin:
#IgnoreExtraProperties
data class DeviceType(
var manufacturer: String? = null,
var marketName: String? = null,
var model: String? = null
) {
constructor(context: Context) : this(
context.deviceType()?.manufacturer,
context.deviceType()?.marketName,
context.deviceType()?.model
)
override fun toString(): String {
val stringSuffix =
if (marketName == model)
""
else
" ($model)"
return model?.let { "$manufacturer $marketName$stringSuffix" }
?: "No device type recognized!"
}
}
/**
* return DeviceType "from" UI Context
*/
fun Context.deviceType(): DeviceType? = runBlocking {
withContext(Dispatchers.IO) {
/*
delay(1000L)
DeviceType("Nokia","Banana","R2D2")
^
This works!
*/
DeviceName
.with(this#deviceType)
.awaitWith(this#deviceType)
// ^ that doesn't!
}
}
suspend fun DeviceName.Request.awaitWith(context: Context): DeviceType? = suspendCoroutine { cont ->
DeviceName.with(context).request { info, error ->
if (error == null) {
cont.resume(DeviceType(
info.manufacturer,
info.marketName,
info.model
))
} else
cont.resumeWithException(Throwable(error.message))
.let {
Log.e(
"FirebaseUserData",
"DeviceName.Request.awaitWith(): $error.message"
)
}
}
}
Executing deviceType().toString()) in MainActivity makes infinite looping in runBlocking() function.
The fundamental question is of course "why my implementation of awaitWith() does not work?", but I am also interested, taking first steps in kotlin and coroutines, if I should provide additional solutions for exception handling, as I read the "coroutines may hide exceptions".
And one more question:
Is Dispatcher.IO here OK? DeviceName gets data from Google API json query.
Should I use that dispatcher type also for coroutines related to firebase DB?
First of all, responding to the question's title, the loop is happening because the constructor is calling Context.deviceType() that calls DeviceName.Request.awaitWith that calls the constructor again:
cont.resume(DeviceType(
info.manufacturer,
info.marketName,
info.model
))
The Context.deviceType() return a DeviceType by itself, but you desire to use it to configure each attribute in the initialization. Each DeviceType's attribute initialization instantiate a DeviceType which each attribute instantiate another DeviceType and so on....
Using Dispatcher.IO is OK and even desired when it comes to IO operations, like network, but you are not quite using it.
The runBlocking call blocks the current thread. The way you are using is like that:
## Assume we are on Thread (A)
fun Context.deviceType(): DeviceType? = runBlocking { ## Still in thread (A)
withContext(Dispatchers.IO) { ## Execute in an IO thread pool, but (A) is waiting
DeviceName
.with(this#deviceType)
.awaitWith(this#deviceType)
} ## Returns to thread (A)
} # Resumes Thread (A)
So, although this is kinda running in an IO dispatcher, the calling thread is blocked until the execution is finished, making it synchronous and indifferent.
Actually my goal was to see the deviceType() function output in non-coroutine environment. This function will be used anyway in other suspend functions or coroutine scope.
This is DeviceType class with its public functions without additional constructor:
#IgnoreExtraProperties
data class DeviceType(
var manufacturer: String? = null,
var marketName: String? = null,
var model: String? = null
) {
override fun toString(): String {
val stringSuffix =
if (marketName == model)
""
else
" ($model)"
return model?.let { "$manufacturer $marketName$stringSuffix" }
?: "No device type recognized!"
}
}
fun Context.deviceTypeByRunBlocking(): DeviceType? = runBlocking {
withContext(Dispatchers.IO) {
DeviceName
.with(this#deviceTypeNoSuspend)
.awaitWith(this#deviceTypeNoSuspend)
}
}
suspend fun Context.deviceType(): DeviceType? =
DeviceName
.with(this#deviceType)
.awaitWith(this#deviceType)
private suspend fun DeviceName.Request.awaitWith(context: Context): DeviceType? =
suspendCoroutine { cont ->
DeviceName.with(context).request { info, error ->
if (error == null) {
cont.resume(
DeviceType(
info.manufacturer,
info.marketName,
info.model
)
//.also{Log.d("TAG","Inside awaitWith(): $it")}
)
} else
cont.resumeWithException(Throwable(error.message))
.let {
Log.e(
"TAG",
"DeviceName.Request.awaitWith(): $error.message"
)
}
}
}
Main Activity:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GlobalScope.launch { Log.d("MainActivity", "${this#MainActivity.deviceType()}") }
//^ this works
Log.d("MainActivity", "${this.deviceTypeByRunBlocking()}")
//^ this still does not, loops in joinBlocking(), isComplete = false
}
}
I know that using GlobalScope is not recommended, but for testing it is fine for me.

How to get exception in init block kotlin

following the code, in init function I create a Person object,and have an exception,now I want to stop the progress in catch like java return. How can I do it?
class Person {
val age: String = "10"
private lateinit var person: Person
init {
try {
person = get(2)
} catch (exception: Throwable) {
}
println("----------------do it $person.age")
}
fun get(i: Int): Person {
when (i) {
1 -> {
return Person()
}
else -> {
throw MyException("aaaaaaaaa")
}
}
}
}
If an instance cannot be created due to errors in init, this error shouldn't be suppressed but delegated to the caller. So just do not catch the exception and the init "stopps" automatically.

Categories

Resources