I have this problem for the coroutines await
. I think the solution is to mock the task because the coroutines await()
basically wraps the listeners. What I did was to mock the simplest path the await
.
/**
* Mocks the simplest behaviour of a task so .await() can return task or throw exception
* See more on [await] and inside of that on awaitImpl
*/
private fun mockTask(exception: Exception? = null): Task<Void> {
val task: Task<Void> = mockk(relaxed = true)
every { task.isComplete } returns true
every { task.exception } returns exception
every { task.isCanceled } returns false
val relaxedVoid: Void = mockk(relaxed = true)
every { task.result } returns relaxedVoid
return task
}
I'm using Mockk in case you are wondering.
This is the explanation. Internally await
simply calls a private awaitImpl
public suspend fun <T> Task<T>.await(): T = awaitImpl(null)
And then awaitImpl
does this
// fast path
if (isComplete) {
val e = exception
return if (e == null) {
if (isCanceled) {
throw CancellationException("Task $this was cancelled normally.")
} else {
@Suppress("UNCHECKED_CAST")
result as T
}
} else {
throw e
}
}
return suspendCancellableCoroutine { cont ->
addOnCompleteListener {...
The comment is in the source code. So that path takes care of resolving before adding a listener which for testing is fine. We only need to control the output. So the mock is saying "enter in the complete conditional and then we control if the exception is null or not. If the exception is not null it will throw it, otherwise, we determined is not canceled so return Void".
This takes me to: you can change Void
, for another type. I was writing test for a Task<Void>
.
And then for using it.
//for success
every { yourGoogleDependency.thatReturnsTask() } returns mockTask()
//for failure
every { yourGoogleDependency.thatReturnsTask() } returns mockTask(Exception())