I have the following code:
suspend fun initialize(sdk: Sdk) =
suspendCoroutine<Unit> { continuation ->
try {
sdk.initialize(
callback = { continuation.resume(Unit) },
onFailure = { error -> continuation.resumeWithException(SdkException(error.message)) })
} catch (exception: Exception) {
continuation.resumeWithException(
SdkException("Crash inside SDK", exception)
)
}
}
The Sdk is a third-party library. I'm using suspendCoroutine to suspend the coroutine and resume when the sdk finishes initializing.
Everything works fine but when I try to write a unit test like this I get the following IllegalStateException: This job has not completed yet. I'm using mockito-kotlin to write the following test:
#Test
fun `should initialize sdk correctly`() = runBlockingTest {
val sdk = mock<Sdk>()
initializeSdk(sdk)
verify(sdk).initialize(any(), any())
}
Basically what I want to do is to be able to test the resume and resumeWithException
Personally I'm not a fan of mocks. If Sdk is an interface, you could just provide your own test implementation to perform your tests (for success and error results). You can control exactly when/if the callback is called etc.
If Sdk is a class that you can't control, you could create an interface to abstract the Sdk class away, and make your initialize method use your own interface instead. However I have to admit this is not ideal.
If you stick with mocking, usually mocking libraries have a way for you to use invocation arguments to mock responses to method calls. With Mockito, it should be something like:
val sdk = mock<Sdk> {
on { initialize(any(), any()) } doAnswer { invocation ->
#Suppress("UNCHECKED_CAST")
val successCallback = invocation.arguments[0] as (() -> Unit) // use callback's function type here
successCallback.invoke()
}
}
(although I'm not an expert in Mockito, so there may be more concise or type-safe ways :D)
Related
I have a coroutine in my viewModel which runs perfectly fine. When I try to unit test the same, it throws the following error "Could not create instance for [type:Factory,primary_type:..MyService"
I am injecting a service and making an API call which works fine while unit testing. If the API fails, I am retrying the same API call with a new instance of Service with different parameters. This works fine in my application but fails in unit test. Here is the following code:
coroutineScope.launch {
try {
var getResponse = myApi?.getCodeApi()
if (getResponse?.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
// Retrying with instance of service with a different token
val newMyApiService: MyService? by inject { parametersOf(newToken) }
getResponse = newMyApiService?.getCodeApi()
}
checkResponse(getResponse)
} catch (exception: Exception) {
Timber.e(exception)
}
}
Is there a way to fix this?. I have taken all the required measures like startingKoinApp for the test environment, also included the required Koin modules before starting to test.
A part of unit test looks something like this
whenever(myAPi.getCodeApi()).thenReturn(properResponse)
val errorResponse : Response<DataModel> = mock()
whenever(response.code()).thenReturn(HttpsURLConnection.HTTP_UNAUTHORIZED)
whenever(myAPi.getCodeApi()).thenReturn(errorResponse)
This can be fixed by replacing the line
val newMyApiService: MyService? by inject { parametersOf(newToken) }
with
val newMyApiService: getNewService(newToken)
The new method will be
fun getNewService(newToken: String): MyService? {
return MyService? by inject { parametersOf(newToken) }
}
Now within your unit test, you can mock the method using power mockito
val underTestsSpy = PowerMockito.spy(underTests)
PowerMockito.doReturn(myserviceApi).`when`(underTestsSpy,
"getNewService", newToken)
Through this, you can mock a new service instance that is created within the method being tested
The experimental implementation of launchIn throws an error for not implementing within a suspend function. I've filed an issue to see if this behavior is intended.
Error
Suspend function 'getFeed' should be called only from a coroutine or another suspend function
However, because launchIn is the creator of a Coroutine this error does not seem valid.
feedRepository.getFeed().onEach { results ->
when (results.status) {
LOADING -> ...
SUCCESS -> withContext(Dispatchers.Main) {
_feedViewState._feed.value = results.data
}
ERROR -> ...
}
}
.flowOn(Dispatchers.IO)
.launchIn(viewModelScope)
Original implementation
viewModelScope.launch(Dispatchers.IO) {
feedRepository.getFeed().collect { results ->
when (results.status) {
LOADING -> ...
SUCCESS -> withContext(Dispatchers.Main) {
_feedViewState._feed.value = results.data
}
ERROR -> ...
}
}
}
The issue has been resolved.
The problem was that the getFeed method, was implemented with the suspend syntax. suspend is not needed when returning a Flow, because Flow is run declaratively, meaning getFeed defines the code that will be run when called. The code will run when launchIn initiates it rather than being run imperatively when the method is first called by itself.
This concept is defined well in this talk, KotlinConf 2019: Asynchronous Data Streams with Kotlin Flow by Roman Elizarov
Before
suspend fun getFeed() = flow { ... }
After
fun getFeed() = flow { ... }
I've recently dove into Kotlin coroutines
Since I use a lot of Google's libraries, most of the jobs is done inside Task class
Currently I'm using this extension to suspend coroutine
suspend fun <T> awaitTask(task: Task<T>): T = suspendCoroutine { continuation ->
task.addOnCompleteListener { task ->
if (task.isSuccessful) {
continuation.resume(task.result)
} else {
continuation.resumeWithException(task.exception!!)
}
}
}
But recently I've seen usage like this
suspend fun <T> awaitTask(task: Task<T>): T = suspendCoroutine { continuation ->
try {
val result = Tasks.await(task)
continuation.resume(result)
} catch (e: Exception) {
continuation.resumeWithException(e)
}
}
Is there any difference, and which one is correct?
UPD: second example isn't working, idk why
The block of code passed to suspendCoroutine { ... } should not block a thread that it is being invoked on, allowing the coroutine to be suspended. This way, the actual thread can be used for other tasks. This is a key feature that allows Kotlin coroutines to scale and to run multiple coroutines even on the single UI thread.
The first example does it correctly, because it invokes task.addOnCompleteListener (see docs) (which just adds a listener and returns immediately. That is why the first one works properly.
The second example uses Tasks.await(task) (see docs) which blocks the thread that it is being invoked on and does not return until the task is complete, so it does not allow coroutine to be properly suspended.
One of the ways to wait for a Task to complete using Kotlin Coroutines is to convert the Task object into a Deferred object by applying Task.asDeferred extension function. For example for fetching data from Firebase Database it can look like the following:
suspend fun makeRequest() {
val task: Task<DataSnapshot> = FirebaseDatabase.getInstance().reference.get()
val deferred: Deferred<DataSnapshot> = task.asDeferred()
val data: Iterable<DataSnapshot> = deferred.await().children
// ... use data
}
Dependency for Task.asDeferred():
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.5.2'
To call suspend function we need to launch a coroutine:
someCoroutineScope.launch {
makeRequest()
}
someCoroutineScope is a CoroutineScope instance. In android it can be viewModelScope in ViewModel class and lifecycleScope in Activity or Fragment, or some custom CoroutineScope instance. Dependencies:
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0'
I'm trying to use Kotlin's coroutines to avoid callback hell, but it doesnt look like I can in this specific situation, I would like some thougths about it.
I have this SyncService class which calls series of different methods to send data to the server like the following:
SyncService calls Sync Student, which calls Student Repository, which calls DataSource that makes a server request sending the data through Apollo's Graphql Client.
The same pattern follows in each of my features:
SyncService -> Sync Feature -> Feature Repository -> DataSource
So every one of the method that I call has this signature:
fun save(onSuccess: ()-> Unit, onError:()->Unit) {
//To Stuff here
}
The problem is:
When I sync and successfully save the Student on server, I need to sync his enrollment, and if I successfully save the enrollment, I need to sync another object and so on.
It all depends on each other and I need to do it sequentially, that's why I was using callbacks.
But as you can imagine, the code result is not very friendly, and me and my team starting searching for alternatives to keep it better. And we ended up with this extension function:
suspend fun <T> ApolloCall<T>.execute() = suspendCoroutine<Response<T>> { cont ->
enqueue(object: ApolloCall.Callback<T>() {
override fun onResponse(response: Response<T>) {
cont.resume(response)
}
override fun onFailure(e: ApolloException) {
cont.resumeWithException(e)
}
})
}
But the function in DataSource still has a onSuccess() and onError() as callbacks that needs to be passed to whoever call it.
fun saveStudents(
students: List<StudentInput>,
onSuccess: () -> Unit,
onError: (errorMessage: String) -> Unit) {
runBlocking {
try {
val response = GraphQLClient.apolloInstance
.mutate(CreateStudentsMutation
.builder()
.students(students)
.build())
.execute()
if (!response.hasErrors())
onSuccess()
else
onError("Response has errors!")
} catch (e: ApolloException) {
e.printStackTrace()
onError("Server error occurred!")
}
}
}
The SyncService class code changed to be like:
private fun runSync(onComplete: () -> Unit) = async(CommonPool) {
val syncStudentProcess = async(coroutineContext, start = CoroutineStart.LAZY) {
syncStudents()
}
val syncEnrollmentProcess = async(coroutineContext, start = CoroutineStart.LAZY) {
syncEnrollments()
}
syncStudentProcess.await()
syncEnrollmentProcess.await()
onComplete()
}
It does execute it sequentially, but I need a way to stop every other coroutine if any got any errors. Error that might come only from Apollo's
So I've been trying a lot to find a way to simplify this code, but didn't get any good result. I don't even know if this chaining of callbacks can be simplify at all. That's why I came here to see some thoughts on it.
TLDR: I want a way to execute all of my functions sequentially, and still be able to stop all coroutines if any got an exception without a lot o chaining callbacks.
I am testing Kotlin coroutines in my Android app and I am trying to do the following unit test
#Test fun `When getVenues success calls explore venues net controller and forwards result to listener`() =
runBlocking {
val near = "Barcelona"
val result = buildMockVenues()
val producerJob = produce<List<VenueModel>>(coroutineContext) { result.value }
whenever(venuesRepository.getVenues(eq(near))) doReturn producerJob // produce corooutine called inside interactor.getVenues(..)
interactor.getVenues(near, success, error) // call to real method
verify(venuesRepository).getVenues(eq(near))
verify(success).invoke(argThat {
value == result.value
})
}
The interactor method is as follows
fun getVenues(near: String, success: Callback<GetVenuesResult>,
error: Callback<GetVenuesResult>) =
postExecute {
repository.getVenues(near).consumeEach { venues ->
if (venues.isEmpty()) {
error(GetVenuesResult(venues, Throwable("No venues where found")))
} else {
success(GetVenuesResult(venues))
}
}
}
postExecute{..} is a method on a BaseInteractor that executes the function in the ui thread through a custom Executor that uses the launch(UI) coroutine from kotlin android coroutines library
fun <T> postExecute(uiFun: suspend () -> T) =
executor.ui(uiFun)
Then the repository.getVenues(..) function is also a coroutine that returns the ProducerJob using produce(CommonPool) {}
The problem is that it seams that success callback in the interactor function doesn't seem to be executed as per the
verify(success).invoke(argThat {
value == result.value
})
However, I do see while debugging that the execution in the interactor function reaches to the if (venues.isEmpty()) line inside the consumeEach but then from there exits and continues with the test, obviously failing on the verify for the success callback.
I am a bit new on coroutines so any help would be appreciated.
I figured this one out. I saw that the problem was just with this producing coroutine and not with the others tests that are also using coroutines and working just fine. I noticed that I actually missed the send on the mocked ProducingJob in order to have it actually produce a value, in this case the list of mocks. I just added that changing the mock of the producing job to
val producerJob = produce { send(result.value) }