I'm trying to mock a Retrofit Response when it isn't successful.
interface ServiceInterface {
#POST("auth/login")
suspend fun loginRequest(#Body loginInformation: LoginInformation) : Response<LoginResponse>}
I made an onSuccess test and it worked, but I don't know how to mock a Response when it isn't successful
#Test
fun onCallSuccess() = runBlocking {
val response = Response.success(LoginResponse(status = "OK", token = "TOKEN"))
coEvery {
serviceMock.loginRequest(any())
} returns response
loginRepository.doLogin("", "")
coVerify {
serviceMock.loginRequest(any())
}
assertEquals(LoginResponse(status = "OK", token = "TOKEN"), loginRepository.doLogin("",""))
}
From the retrofit documentation:
public static <T> Response<T> error(int code, okhttp3.ResponseBody body)
Create a synthetic error response with an HTTP status code of code and
body as the error body.
You should be able to make another test using Response.error similar to how you did with Response.success
Related
I'm implementing Twitter OAuth flows as per:
https://developer.twitter.com/en/docs/authentication/guides/log-in-with-twitter
I am getting a response back for the first step (oauth/request_token) which has a 200 code, but the response body is completely empty.
I'm using Retrofit to call the API, and have hooked up an interceptor OkHttpClient to debug the response like so:
val client = OkHttpClient.Builder().also { builder ->
builder.addInterceptor { chain ->
val request = chain.request()
val response = chain.proceed(request)
response
}
}.build()
Then setting up Retrofit like so:
Retrofit.Builder()
.baseUrl(TWITTER_AUTH_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
.create(TwitterAuthRetrofit::class.java)
.getRequestToken(
authorizationHeaders
).enqueue(object : Callback<TwitterRequestToken> {
override fun onResponse(call: Call<TwitterRequestToken>, response: Response<TwitterRequestToken>) {
onSuccess(response.body())
}
override fun onFailure(call: Call<TwitterRequestToken>, t: Throwable) {
onFailure()
}
})
When I debug in the interceptor, I can see the response is successful (200) but the response body is empty, which I think is causing my Gson deserialization to fail.
The result of calling response.body.contentLength() in the interceptor is -1.
The result of calling response.code in the interceptor is 200.
Here is the model I am attempting to deserialize the response body to:
data class TwitterRequestToken(
#SerializedName(value = "oauth_token")
val token: String,
#SerializedName(value = "oauth_token_secret")
val tokenSecret: String,
#SerializedName(value = "oauth_callback_confirmed")
val callbackConfirmed: Boolean
)
Note I am using #SerializedName to provide the keys for the response body, whilst the names of my properties are arbitrary to our app (we use camel case). I add a GsonConverterFactory to the Retrofit instance using the builder and have done this in the same way for many other requests before with no issues.
Here is the response I am getting from the API, which I am looking at via debugging in the interceptor above:
Response{protocol=h2, code=200, message=, url=https://api.twitter.com/oauth/request_token}
And here is the cause message from the Throwable I am getting in the onFailure callback from Retrofit:
com.google.gson.stream.MalformedJsonException:
Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 path $
Has anyone got any idea what might cause this?
Finally figured it out, hope this helps someone in future...
The response body from the Twitter API for oauth/request_token isn't encoded as JSON; you will need to read it from the response buffer. Specifically, when implementing the API with Retrofit, you will want your Retrofit interface to return ResponseBody (rather than your custom class), remove GSON from the Retrofit builder and, in the onResponseCallback from Retrofit, write the following code to read the buffer to a string, then split the string on & to get each key val pair, then you can split each of these on = and make sure you have all 3 values before constructing your model:
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
response.body()?.also { body ->
body.source().readString(Charsets.UTF_8).split('&').map { param ->
param.split('=').let { keyVal ->
keyVal[0] to keyVal[1]
}
}.toMap().let { paramMap ->
val oauthToken = paramMap["oauth_token"]
val oauthTokenSecret = paramMap["oauth_token_secret"]
val oauthCallbackConfirmed = paramMap["oauth_callback_confirmed"]?.toBoolean()
if (oauthToken == null || oauthTokenSecret == null || oauthCallbackConfirmed == null) {
onFailure()
} else {
onSuccess(
TwitterRequestToken(
oauthToken,
oauthTokenSecret,
oauthCallbackConfirmed
)
)
}
}
} ?: onFailure()
}
I can't seem to get the POST request working with Retrofit. My code:
ApiService.kt contains a function
#Headers("Content-Type: application/json")
#POST("resume")
suspend fun resumeAsync(#Body request: JSONObject): Response<String>
Then in my ListViewModel.kt I have a function
fun resume(id: String) {
coroutineScope.launch {
try {
val paramObject = JSONObject()
paramObject.put("id", id)
val response = Api.retrofitService.resumeAsync(paramObject)
if (response.isSuccessful) {
_status.value = "Success: Resumed!"
}
} catch (e: Exception) {
_status.value = "Failure: " + e.message
}
}
}
Why is this not working? I don't get any error or response back. If I put Log.i in the Api or the view model it says it's triggered
By debugging I found out this error:
2020-09-15 11:40:10.904 20622-20622/com.example.app I/NETWORK: Unable to create #Body converter for class org.json.JSONObject (parameter #1) for method ApiService.resumeAsync
I am using Moshi as well
private val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
private val retrofit = Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl(BASE_URL)
.build()
You can try adding a logging interceptor to your okHttp client and check what you're sending and receiving in the request.
val logging = HttpLoggingInterceptor()
logging.setLevel(HttpLoggingInterceptor.Level.BODY)
Is the url/endpoint correct?
Are you missing a "/" at the end of the url?
Have you declared internet permission in the manifest?
etc.
Solution:
Wrapping request body:
#Body body: RequestBody
val body = RequestBody.create(MediaType.parse("application/json"), obj.toString())
If you need to receive raw json then use Call<*>
#Headers("Content-Type: application/json")
#POST("resume")
fun resumeAsync(#Body request: JSONObject): retrofit2.Call<String>
Inside coroutine (without suspend keyword above)
// or .awaitResponse() to get Response<*> object
val response = Api.retrofitService.resumeAsync(paramObject).await()
Can you debug on this line
if (response.isSuccessful) {
try to check the variable response;
Or you shoud check whether the server is work, check it with other tools like Postman
So I solved my problem by doing what #Paul Nitu recommended
val body = RequestBody.create(MediaType.parse("application/json"), obj.toString())
I am using two kind of interceptor, one is HttpLoggingInterceptor and another one is my custom AuthorizationInterceptor
I am using below updated retrofit version library,
def retrofit_version = "2.7.2"
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
implementation "com.squareup.retrofit2:converter-gson:$retrofit_version"
implementation 'com.squareup.okhttp3:logging-interceptor:4.4.0'
implementation 'com.squareup.okhttp3:okhttp:4.4.0'
below is code
private fun makeOkHttpClient(): OkHttpClient {
val logger = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
return OkHttpClient.Builder()
.addInterceptor(AuthorizationInterceptor(context)) <---- To put Authorization Barrier
.addInterceptor(logger) <---- To log Http request and response
.followRedirects(false)
.connectTimeout(50, TimeUnit.SECONDS)
.readTimeout(50, TimeUnit.SECONDS)
.writeTimeout(50, TimeUnit.SECONDS)
.build()
}
When I try to execute below code, in file named SynchronizationManager.kt, it gives me an error.
var rulesResourcesServices = RetrofitInstance(context).buildService(RulesResourcesServices::class.java)
val response = rulesResourcesServices.getConfigFile(file).execute() <---In this line I am getting an exception... (which is at SynchronizationManager.kt:185)
My RulesResourcesServices class is here
After debug I found that when below function called, at that time I am getting an exception
#GET("users/me/configfile")
fun getConfigFile(#Query("type") type: String): Call<ResponseBody>
I am getting following error
java.lang.IllegalStateException: closed
at okio.RealBufferedSource.read(RealBufferedSource.kt:184)
at okio.ForwardingSource.read(ForwardingSource.kt:29)
at retrofit2.OkHttpCall$ExceptionCatchingResponseBody$1.read(OkHttpCall.java:288)
at okio.RealBufferedSource.readAll(RealBufferedSource.kt:293)
at retrofit2.Utils.buffer(Utils.java:316)<------- ANDROID IS HIGH-LIGHTING
at retrofit2.BuiltInConverters$BufferingResponseBodyConverter.convert(BuiltInConverters.java:103)
at retrofit2.BuiltInConverters$BufferingResponseBodyConverter.convert(BuiltInConverters.java:96)
at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:225)
at retrofit2.OkHttpCall.execute(OkHttpCall.java:188)
at retrofit2.DefaultCallAdapterFactory$ExecutorCallbackCall.execute(DefaultCallAdapterFactory.java:97)
at android.onetap.SynchronizationManager.downloadFile(SynchronizationManager.kt:185)
at android.base.repository.LoginRepository.downloadConfigFilesAndLocalLogin(LoginRepository.kt:349)
at android.base.repository.LoginRepository.access$downloadConfigFilesAndLocalLogin(LoginRepository.kt:48)
at android.base.repository.LoginRepository$loginTask$2.onSRPLoginComplete(LoginRepository.kt:210)
at android.base.repository.LoginRepository$performSyncLogin$srpLogin$1$1.onSRPLogin(LoginRepository.kt:478)
at android.srp.SRPManager$SRPLoginOperation$execute$1.invokeSuspend(SRPManager.kt:323)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:561)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:727)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:667)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:655)
Below is screenshot, in that you can see that, I am getting output of file but don't know why it is throwing an exception.
checked Retrofit's Utils class
https://github.com/square/retrofit/blob/master/retrofit/src/main/java/retrofit2/Utils.java
static ResponseBody buffer(final ResponseBody body) throws IOException {
Buffer buffer = new Buffer();
body.source().readAll(buffer); <-This line throws an error.
return ResponseBody.create(body.contentType(), body.contentLength(), buffer);
}
Update
Same thing is working fine with enqueue method.
response.enqueue(object : Callback<ResponseBody?> {
override fun onResponse(call: Call<ResponseBody?>, response: retrofit2.Response<ResponseBody?>) {
}
})
I have post same issue with Retrofit team, lets see.
https://github.com/square/retrofit/issues/3336
Thanks to JakeWharton (https://github.com/square/retrofit/issues/3336), I can be able to get solution.
Actually in my custom interceptor I was reading response by following code
Response.body().string()
I was doing because above code was helping me to find out that if there is any error than what kind of error it is....
if it is AUTH_ERROR, I have to generate new token and append it to request header.
According to retrofit document, if we call any of below method then response will be closed, which means it's not available to consume by the normal Retrofit internals.
Response.close()
Response.body().close()
Response.body().source().close()
Response.body().charStream().close()
Response.body().byteStream().close()
Response.body().bytes()
Response.body().string()
So to read data, I will use
response.peekBody(2048).string()
instead of
response.body().string(),
so it will not close response.
below is the final code
val response = chain.proceed(request)
val body = response.peekBody(Long.MAX_VALUE).string()//<---- Change
try {
if (response.isSuccessful) {
if (body.contains("status")) {
val jsonObject = JSONObject(body)
val status = jsonObject.optInt("status")
Timber.d("Status = $status")
if (status != null && status == 0) {
val errorCode = jsonObject.getJSONObject("data").optString("error_code")
if (errorCode != null) {
addRefreshTokenToRequest(request)
return chain.proceed(request)
}
}
} else {
Timber.d("Body is not containing status, might be not valid GSON")
}
}
Timber.d("End")
} catch (e: Exception) {
e.printStackTrace()
Timber.d("Error")
}
return response
Extending #Siddhpura Amit's answer:
If you don't know the bytes to pass into peak method then you can still use all of the methods, but will just have to create new Response object.
Inside interceptor:
okhttp3.Response response = chain.proceed(request);
String responseBodyString = response.body().string();
//Do whatever you want with the above string
ResponseBody body = ResponseBody.create(response.body().contentType(), responseBodyString);
return response.newBuilder().body(body).build();
maybe you closed your response in your AuthorizationInterceptor like this
override fun intercept(chain: Interceptor.Chain): Response {
...
val response = chain.proceed(builder.build())
response.close()
...
}
I've created a POST request using rxjava and retrofit that successfully hits my backend server and logs the user in (I get a 201 response in my console, all good). However, I want to then retrieve the users access token that is returned, but when I try to access the rxjava result, it just gives me the object I passed to it. Nowhere can I find out how to get the json success response. I have also verified there is in fact a response in Postman, so it's something with how I make this call.
Here is my Retrofit portion
#Headers("Content-Type: application/json")
#POST("api/v1/login")
fun loginTask(#Body credentials: UserLogin)
: Observable<UserLogin>
And the correspoinding API function
class ApiFunctions(val apiService: LunchVoteApi) {
fun provideHello(): io.reactivex.Observable<Hello> {
return apiService.helloMessage()
}
fun loginTask(email: String, password: String): io.reactivex.Observable<UserLogin> {
val credentials: UserLogin = UserLogin(email, password)
return apiService.loginTask(credentials)
}
}
The UserLogin model that is deserialized by Gson
data class UserLogin(
#SerializedName("email") val email: String,
#SerializedName("password") val password: String
)
And finally the call in my LoginActivity
val loginTask = ApiProvider.provideLoginTask()
override fun doInBackground(vararg params: Void): Boolean? {
// TODO: attempt authentication against a network service.
try {
// Simulate network access.
// Thread.sleep(2000)
compositeDisposable.add(
loginTask.loginTask(mEmail, mPassword)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe ({
result ->
System.out.println(result.toString())
}, { error ->
System.out.println(error)
})
)
} catch (e: InterruptedException) {
return false
}
The confusion comes when I try to access the result -> portion of the compositeDisposable call. It just prints out the UserLogin object. Am I missing something here? Thanks.
Turns out I was returning my UserLogin type instead of a pojo object with an access token property.
Changing my retrofit call to #Headers("Content-Type: application/json")
#POST("api/v1/login")
fun loginTask(#Body credentials: UserLogin)
: Observable<AccessToken>
And creating a new model
data class AccessToken(
#SerializedName("accessToken") val email: String
)
I am now able to print out the token. Thanks to #john-oreilly
I'm using Retrofit to make a POST Request in my web server.
However, I can't seem to get the response body when the response status is 422 (unprocessable entity). The response body is always null.
I want to know if I'm doing something wrong or if there's a workaround for this. Because I'm using the same json in the request with Postman, and it returns the body normally.
This is the method:
#Headers("Content-Type: application/vnd.api+json")
#POST("my_endpoint")
Call<JsonObject> postEntry(#Header("Authorization") String authorization, #Body JsonObject json);
The body is a JsonObject, I'm not serializing like the documentation say. But I don't think this is the problem.
By default, when your server is returning an error code response.body() is always null. What you are looking for is response.errorBody(). A common approach would be something like this:
#Override
public void onResponse(Response<JsonObject> response, Retrofit retrofit) {
if (response.isSuccess()) {
response.body(); // do something with this
} else {
response.errorBody(); // do something with that
}
}
If you need something advanced take a look at Interceptors and how to use them
I got the same error. My API was working using POSTMAN request but not working from Android retrofit call.
At first I was trying using #Field but it was getting error but later I've tried with #Body and it worked.
Sample Retrofit interface call
#POST("api/v1/app/instance")
Call<InstanceResponse> updateTokenValue(
#HeaderMap Map<String, String> headers,
#Body String body);
and API calling code is:
Map<String, String> headerMap=new HashMap<>();
headerMap.put("Accept", "application/json");
headerMap.put("Content-Type", "application/json");
headerMap.put("X-Authorization","access_token");
Map<String, String> fields = new HashMap<>();
fields.put("app_name", "video");
fields.put("app_version", "2.0.0");
fields.put("firebase_token", "token");
fields.put("primary", "1");
ApiInterface apiInterface = ApiClient.getApiClient().create(ApiInterface.class);
Call<InstanceResponse> call = apiInterface.updateTokenValue(
headerMap,new Gson().toJson(fields));
Well in this case you'll have to convert the response.
Have a look at this link
All the steps are already provided in the link above.
For Kotlin users here is the code solution.
ErrorResponse.kt (This obviously depends on your error response)
import com.squareup.moshi.Json
data class ErrorResponse(
#Json(name="name")
val name: String? = null,
#Json(name="message")
val message: String? = null,
#Json(name="errors")
val errors: Errors? = null,
#Json(name="statusCode")
val statusCode: Int? = null
)
ApiFactory.kt (Let me know if you need the entire code)
fun parseError(response: Response<*>): ErrorResponse {
val converter = ApiFactory.retrofit()
.responseBodyConverter<ErrorResponse>(
ErrorResponse::class.java, arrayOfNulls<Annotation>(0)
)
val error: ErrorResponse
try {
error = converter.convert(response.errorBody()!!)!!
} catch (e: IOException) {
e.printStackTrace()
return ErrorResponse()
}
return error
}
and in the Presenter (I use MVP)
GlobalScope.launch(Dispatchers.Main) {
try {
val response = ApiFactory.apiService.LOGIN(username, password)
.await()
val body = response.body()
body?.let {
// Handle success or any other stuff
if (it.statusCode == 200) {
mView.onSuccess(it.data!!)
}
} ?:
// This is the else part where your body is null
// Here is how you use it.
// Pass the response for error handling
mView.showMessage(ApiFactory.parseError(response).message!!)
} catch (e: Exception) {
e.printStackTrace()
}
}
And thats how you roll it!
That's All Folks!