retrofit-mock's BehaviorDelegate throws an exception when attempting to return for a suspend function.
For example
interface SomeApi {
@GET("foo")
suspend fun getFoo(): Foo
}
will throw when attempting to use:
val delegate: BehaviorDelegate<SomeApi> = mockRetrofit.create(SomeApi::class.java)
val foo: Foo = delegate.returningResponse(Foo()).getFoo()
I'd submit a failing test for this, but it would involve me pulling in Kotlin into the retrofit-mock module which I'm not sure is desirable?
Exception thrown looks like:
java.lang.IllegalArgumentException: Could not locate call adapter for class java.lang.Object.
Tried:
* retrofit2.CompletableFutureCallAdapterFactory
* retrofit2.DefaultCallAdapterFactory
at retrofit2.Retrofit.nextCallAdapter(Retrofit.java:241)
at retrofit2.Retrofit.callAdapter(Retrofit.java:205)
at retrofit2.mock.BehaviorDelegate$1.invoke(BehaviorDelegate.java:64)
at java.lang.reflect.Proxy.invoke(Proxy.java:1006)
at $Proxy0.getGames(Unknown Source)
at au.com.gridstone.debugdrawer.sampleapp.MockGamesApi.getGames(MockGamesApi.kt:17)
at au.com.gridstone.debugdrawer.sampleapp.GamesViewModel$refresh$1.invokeSuspend(GamesViewModel.kt:43)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:238)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7319)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:934)
I'm facing the same issue and created a test for it in my fork. I'm still investigating it to figure out what is happening
I'm facing the same issue here and can not mock anything with coroutines.
I think MockRetrofit is not updated to work with coroutines like Retrofit...
Yep this needs to be special-cased the same way it was inside regular Retrofit.
Should be great to add the Retrofit.create() extension to MockRetrofit too.
@JakeWharton is there any place to check the advances in this issue? Maybe some sonatype snapshot?
Updates will occur on this issue. For now, it remains a bug.
On Sun, Sep 15, 2019, 7:43 PM Javier Segovia Córdoba <
[email protected]> wrote:
@JakeWharton https://github.com/JakeWharton is there any place to check
the advances in this issue? Maybe some sonatype snapshot?—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/square/retrofit/issues/3148?email_source=notifications&email_token=AAAQIEIO3ATGAU6HQKMSPTDQJ3CDXA5CNFSM4H6KDRIKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD6X3QSA#issuecomment-531609672,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAAQIEJZF3X6KQUMWVTQSGTQJ3CDXANCNFSM4H6KDRIA
.
Have same issue for regular retrofit without any retrofit-mock's
@GET("url")
suspend fun getClient(@Header("email") email: String): ClientPayload
for method Api.getClient
at retrofit2.Utils.methodError(Utils.java:52)
at retrofit2.HttpServiceMethod.createCallAdapter(HttpServiceMethod.java:105)
at retrofit2.HttpServiceMethod.parseAnnotations(HttpServiceMethod.java:66)
at retrofit2.ServiceMethod.parseAnnotations(ServiceMethod.java:37)
at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:170)
at retrofit2.Retrofit$1.invoke(Retrofit.java:149)
at java.lang.reflect.Proxy.invoke(Proxy.java:397)
at $Proxy0.getClient(Unknown Source)
at com..android.data.ClientRegistrationRepository.getClient(ClientRegistrationRepository.kt:23)
at com..android.domain.client.ClientNameUseCase$updateClientNameSettings$2.invokeSuspend(ClientNameUseCase.kt:21)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:241)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:740)
Caused by: java.lang.IllegalArgumentException: Could not locate call adapter for class java.lang.Object.
Tried:
* retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
* retrofit2.DefaultCallAdapterFactory
at retrofit2.Retrofit.nextCallAdapter(Retrofit.java:241)
at retrofit2.Retrofit.callAdapter(Retrofit.java:205)
at retrofit2.HttpServiceMethod.createCallAdapter(HttpServiceMethod.java:103)
... 13 more
I am having the same problem as well without retrofit-mock.
@GET("url")
suspend fun getItem(): SomeResponse
java.lang.IllegalArgumentException: Unable to create call adapter for class java.lang.Object
for method SomeService.getItem
at retrofit2.ServiceMethod$Builder.methodError(ServiceMethod.java:755)
at retrofit2.ServiceMethod$Builder.createCallAdapter(ServiceMethod.java:240)
at retrofit2.ServiceMethod$Builder.build(ServiceMethod.java:165)
at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:170)
at retrofit2.Retrofit$1.invoke(Retrofit.java:147)
at java.lang.reflect.Proxy.invoke(Proxy.java:1006)
at $Proxy3.getItem(Unknown Source)
at ...
at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:91)
at kotlinx.coroutines.CoroutineScopeKt.coroutineScope(CoroutineScope.kt:186)
at ...
at ...
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.ResumeModeKt.resumeUninterceptedMode(ResumeMode.kt:45)
at kotlinx.coroutines.internal.ScopeCoroutine.onCompletionInternal$kotlinx_coroutines_core(Scopes.kt:28)
at kotlinx.coroutines.JobSupport.completeStateFinalization(JobSupport.kt:305)
at kotlinx.coroutines.JobSupport.tryFinalizeFinishingState(JobSupport.kt:230)
at kotlinx.coroutines.JobSupport.tryMakeCompleting(JobSupport.kt:799)
at kotlinx.coroutines.JobSupport.makeCompletingOnce$kotlinx_coroutines_core(JobSupport.kt:742)
at kotlinx.coroutines.AbstractCoroutine.resumeWith(AbstractCoroutine.kt:117)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:233)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Caused by: java.lang.IllegalArgumentException: Could not locate call adapter for class java.lang.Object.
Tried:
* com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory
* retrofit2.ExecutorCallAdapterFactory
at retrofit2.Retrofit.nextCallAdapter(Retrofit.java:241)
at retrofit2.Retrofit.callAdapter(Retrofit.java:205)
at retrofit2.ServiceMethod$Builder.createCallAdapter(ServiceMethod.java:238)
... 28 more
I was able to reproduce this bug on the latest version of Retrofit (v2.6.2) while I was testing the latest support for coroutines. It would be great to have mock responses working, too.
I'm playing a bit with Retrofit code to implement a fix, but some code related to suspend functions support is package private in Retrofit. @JakeWharton is someone working on a fix? What would be an acceptable solution for this?
No one is working on it. Feel free to add support and send a PR!
On Sat, Oct 26, 2019, 2:31 PM Rafael Toledo notifications@github.com
wrote:
I'm playing a bit with Retrofit code to implement a fix, but some code
related to suspend functions support is package private in Retrofit.
@JakeWharton https://github.com/JakeWharton is someone working on a
fix? What would be an acceptable solution for this?—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/square/retrofit/issues/3148?email_source=notifications&email_token=AAAQIELQAXFG5WGD4RBESE3QQSEJBA5CNFSM4H6KDRIKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOECKOEIQ#issuecomment-546628130,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AAAQIEJ4HUEDQGIBDNZK6RTQQSEJBANCNFSM4H6KDRIA
.
@rafaeltoledo We have the blessing. It's up to us. Show me what you've got!
I took a look at the logs and I believe I have found the root cause and special casing that Jake mentioned earlier.
It looks like in HttpServiceMethod we check for requestFactory.isKotlinSuspensionFunction to determine which adapterType to use; i.e. the coroutine Response Type or a Generic Return Type.
The adapterType is then used by a private method called createCallAdapter() to return an adapter appropriate for a couroutine Response or a Generic Response.
In the Mock Retrofit BehaviorDelegate, we naively use the Generic Response Type to create a Call Adapter which is why everyone sees java.lang.IllegalArgumentException: Could not locate call adapter for class java.lang.Object in their logs.
Rather than expose the private method for createCallAdapter, can we think of a reason why the requestFactory.isKotlinSuspensionFunction is not being hit or cannot be used by Mock Retrofit? This is where someone else's knowledge of the library will come in handy.
@vrickey123 I'm not too familiar with Retrofit-Mock internals too. But I did some experiments, and now my current state is the test failing with java.lang.ClassCastException: class retrofit2.mock.BehaviorCall cannot be cast to class java.lang.String (retrofit2.mock.BehaviorCall is in unnamed module of loader 'app'; java.lang.String is in module java.base of loader 'bootstrap'). You can check the master branch of my fork.
Most helpful comment
Yep this needs to be special-cased the same way it was inside regular Retrofit.