Alexandr Zhurkov
Alexandr Zhurkov

Reputation: 504

Mock Android Firebase Task within Kotlin coroutine

I am working with Android project that needs to initialises Firebase token. Mostly working with Kotlin coroutines I would like to implement code line by line, but since there is no way to synchronously request Firebase token I came up with the solution to wrap OnCompleteListener using following function:

private suspend fun fcmCallbackSuspendWrapper(block: (OnCompleteListener<InstanceIdResult>) -> Unit)
        = suspendCancellableCoroutine<Task<InstanceIdResult>> { cont ->
    block(OnCompleteListener { response ->
        cont.resume(response)
    })
}

It basically blocks current thread until callback is called by Task. Then I'm applying said function in my code in following way:

val fcmInstance = fcmCallbackSuspendWrapper { listener ->
    fireBaseInstanceIdProvider
        .provide() // Returns Firebase instance 
        .instanceId // Returns Task<InstanceIdResult>
        .addOnCompleteListener(listener) // Sets listener to block current thread until completed
}

But now I need to add unit test to the code above and here I encounter several problems:

@Mock lateinit var instanceIdResultTaskMock: Task<InstanceIdResult>
@Mock lateinit var fireBaseInstanceIdProviderMock: Provider<FirebaseInstanceId>
@Mock lateinit var firebaseInstanceIdMock: FirebaseInstanceId

...

Mockito.`when`(fireBaseInstanceIdProviderMock.provide()).thenReturn(firebaseInstanceIdMock)
Mockito.`when`(firebaseInstanceIdMock.instanceId).thenReturn(instanceIdResultTaskMock)

But since Task<InstanceIdResult> is mocked - it's OnCompleteListener<TResult> is never called and thread is blocked forever, preserving code execution to continue. What can be done in this situation?

Upvotes: 5

Views: 2790

Answers (2)

Jo&#235;l Defante
Jo&#235;l Defante

Reputation: 91

Thanks @mcatta your snippet helped me :)

However since FirebaseIntanceId is deprecated, this is the new way to do it :

mockkStatic("com.google.firebase.messaging.FirebaseMessaging")
       
val firebaseMessagingMock = mockk<FirebaseMessaging>()
every { getInstance() } returns firebaseMessagingMock

val mockGetTokenTask = mockk<Task<String>>()
every { firebaseMessagingMock.token} returns mockGetTokenTask

val slot = slot<OnCompleteListener<String>>()
every { mockGetTokenTask.addOnCompleteListener(capture(slot)) } answers {
  slot.captured.onComplete(mockGetTokenTask)
  mockGetTokenTask
}

every {mockGetTokenTask.exception} returns null
every {mockGetTokenTask.result} returns currentToken

Upvotes: 9

mcatta
mcatta

Reputation: 491

I have done it by using Mockk.io, this is my code snippet, with mockito I think you may do the same thing

val mockedResult = mockk<InstanceIdResult>()
every { mockedResult.token } returns "token"

val mockedInstanceId = mockk<Task<InstanceIdResult>>()
every { mockedInstanceId.isSuccessful } returns true
every { mockedInstanceId.result } returns mockedResult

val slot = slot<OnCompleteListener<InstanceIdResult>>()
every { mockedInstanceId.addOnCompleteListener(capture(slot)) } answers {
    slot.captured.onComplete(mockedInstanceId)
    mockedInstanceId
}

every { firebaseInstanceId.instanceId } returns mockedInstanceId

Upvotes: 9

Related Questions