I try to use MockWebServer to the various responses to my API.
I have made a simple example just to try that what I would like to do is a working method.
Isn't the mockWebServer meant to 'mock' the endpoint of my http connections? Like a real server? Whenever I try to make a call I got UnknownHostException.
Am I using it wrong? Isn't it supposed to just replace a server's response? (mock it)
E D I T:
I have internet permission in manifest.
I use:
androidTestImplementation 'com.squareup.okhttp3:mockwebserver:3.9.1'
Code:
#Test
public void myTest() throws IOException, InterruptedException {
String url = "http://some-mock-url.com";
MockWebServer server = new MockWebServer();
server.enqueue(new MockResponse().setBody("Something not valid JSON response"));
server.start();
server.url(url);
final CountDownLatch signal = new CountDownLatch(1);
AndroidNetworking.get(url)
.addQueryParameter("some_key", "some_value12345")
.addHeaders("token", "token_1231234")
.build()
.getAsJSONObject(new JSONObjectRequestListener() {
#Override
public void onResponse(JSONObject response) {
signal.countDown();
Log.i("Response", "jsonObject: " + response);
}
#Override
public void onError(ANError anError) {
signal.countDown();
Log.i("Response", "error: " + anError.getMessage());
}
}
);
signal.await();
}
Logcat:
com.androidnetworking.error.ANError: java.net.UnknownHostException:
Unable to resolve host "some-mock-url.com": No address associated with
hostname
I have found an example when the order is reversed.
So instead of setting an URL to the Mock server, you have to set the Mock server's url to the actual URL, so like:
myAPIsURL= mockWebServer.url("/").toString();
And not
mockWebServer.url(myAPIsURL);
In case someone has the same problem, MockWebServer is from the same dependency of OKHttp so they both need to have the same dependency version.
It was a proxy issue for me .
i followed this solution.
I am not getting the unknown host exception now.
https://stackoverflow.com/a/41714851/5856146
Related
I'm trying to use Retrofit2 to create a GET petition for my Android app. I have followed a tutorial on how to create the code and it worked with a webpage that did not need any authentication. Then I tried to adapt the same code to my needs, but I can't get it right. Either I get a 401 error or I get a 500 error.
I want to reach this URL: http://adaptai-eea8.restdb.io/rest/usuarios
So my baseurl is http://adaptai-eea8.restdb.io/.
This is my function, which is in the MainActivity:
private void find(String codigo){
String apikey = "9dc3afb8b6087192d5e9e50c5f2cb44927be5";
Retrofit retrofit = new Retrofit.Builder().baseUrl("http://adaptai-eea8.restdb.io/")
.addConverterFactory(GsonConverterFactory.create()).build();
UsuarioAPI usuarioAPI = retrofit.create(UsuarioAPI.class);
Call<Usuario> call = usuarioAPI.find(codigo);
call.enqueue(new Callback<Usuario>() {
#Override
public void onResponse(Call<Usuario> call, Response<Usuario> response) {
try {
int a = 5;
if(response.isSuccessful()){
Usuario u = response.body();
textView.setText(u.getContra());
Log.d("Funciona", u.getContra());
}
}catch(Exception ex){
Toast.makeText(MainActivity.this, ex.getMessage(), Toast.LENGTH_SHORT);
}
}
#Override
public void onFailure(Call<Usuario> call, Throwable t) {
Toast.makeText(MainActivity.this, "Error de conexión", Toast.LENGTH_SHORT);
}
});
}
And this is the GET petition I am using:
#Headers({"User-Agent: my-restdb-app","Content-Type: application/x-www-form-urlencoded", "x-apikey: heregoestheapikey", "Accept: application/json", "cache-control: no-cache"})
//#FormUrlEncoded
#GET("rest/usuarios/")
//public Call<Usuario> find(#Query("nombre") String nombre);
Call<Usuario> find(#Query("nombre") String nombre);
There has to be something wrong with this code, and maybe it is related to sending the apikey as a header, i don't know. Can someone tell me where am I wrong? Thanks in advance.
If you want to pass the apiKey as header you need to pass it as a parameter like you can see in the docs
#Headers({"User-Agent: my-restdb-app","Content-Type: application/x-www-form-urlencoded", "Accept: application/json", "cache-control: no-cache"})
#GET("rest/usuarios/")
Call<Usuario> find(#Header("x-apikey") String apiKey, #Query("nombre") String nombre);
Additionally, are you sure about the rest of parameters? Like User-Agent being "my-restdb-app" and query param name being "nombre"
I've made little server based on NodeMCU. All works good, when I'm conneting from browser, but problem starts, when I'm trying to connect from Android app uisng OkHttp or Volley, I'm receiving exceptions.
java.io.IOException: unexpected end of stream on Connection using OkHttp,
EOFException using Volley.
Problem is very similar for this
EOFException after server responds, but answer didn't found.
ESP server code
srv:listen(80, function(conn)
conn:on("receive", function(conn,payload)
print(payload)
conn:send("<h1> Hello, NodeMCU.</h1>")
end)
conn:on("sent", function(conn) conn:close() end)
end)
Android code
final RequestQueue queue = Volley.newRequestQueue(this);
final String url = "http://10.42.0.17:80";
final StringRequest request = new StringRequest(Request.Method.GET, url,
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
mTemperatureTextView.setText(response.substring(0, 20));
System.out.println(response);
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
System.out.println("Error + " + error.toString());
mTemperatureTextView.setText("That didn't work!");
}
}
);
mUpdateButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
queue.add(request);
}
});
What you're sending back is not HTTP. It's nothing but a protocol-agnostic HTML fragment. Furthermore, there's a memory leak lingering.
Try this instead:
srv:listen(80, function(conn)
conn:on("receive", function(sck,payload)
print(payload)
sck:send("HTTP/1.0 200 OK\r\nServer: NodeMCU on ESP8266\r\nContent-Type: text/html\r\n\r\n<h1> Hello, NodeMCU.</h1>")
end)
conn:on("sent", function(sck) sck:close() end)
end)
you need to send back some HTTP headers, HTTP/1.0 200 OK and the newlines are mandatory
each function needs to use it's own copy of the passed socket instance, see how I renamed conn to sck in the two callback functions, see https://stackoverflow.com/a/37379426/131929 for details
For a more complete send example look at net.socket:send() in the docs. That becomes relevant once you start sending more than just a couple of bytes.
I'm looking for a way to mock api responses in android tests.
I have read the roboelectric could be used for this but I would really appreciate any advice on this.
After a small bit of looking around on the web I have found MockWebServer to be what I was looking for.
A scriptable web server for testing HTTP clients. This library makes it easy to test that your app Does The Right Thing when it makes HTTP and HTTPS calls. It lets you specify which responses to return and then verify that requests were made as expected.
To get setup just add the following to your build.gradle file.
androidTestCompile 'com.google.mockwebserver:mockwebserver:20130706'
Here is a simple example taking from their GitHub page.
public void test() throws Exception {
// Create a MockWebServer. These are lean enough that you can create a new
// instance for every unit test.
MockWebServer server = new MockWebServer();
// Schedule some responses.
server.enqueue(new MockResponse().setBody("hello, world!"));
// Start the server.
server.play();
// Ask the server for its URL. You'll need this to make HTTP requests.
URL baseUrl = server.getUrl("/v1/chat/");
// Exercise your application code, which should make those HTTP requests.
// Responses are returned in the same order that they are enqueued.
Chat chat = new Chat(baseUrl);
chat.loadMore();
assertEquals("hello, world!", chat.messages());
// Shut down the server. Instances cannot be reused.
server.shutdown();
}
Hope this helps.
MockWebServer didn't work for me with AndroidTestCase. For instance, ECONNREFUSED error happened quite randomly (described in https://github.com/square/okhttp/issues/1069). I didn't try Robolectric.
As of OkHttp 2.2.0, I found an alternative way which worked well for me: Interceptors. I placed the whole mock response inside a json file stored on androidTest/assets/, say, 'mock_response.json'. When I instanced an OkHttp for testing, I exposed an Interceptor which I would rewrite the incoming response. Basically, body() would instead stream the data in 'mock_response.json'.
public class FooApiTest extends AndroidTestCase {
public void testFetchData() throws InterruptedException, IOException {
// mock_response.json is placed on 'androidTest/assets/'
final InputStream stream = getContext().getAssets().open("mock_response.json");
OkHttpClient httpClient = new OkHttpClient();
httpClient.interceptors().add(new Interceptor() {
#Override
public Response intercept(Chain chain) throws IOException {
return new Response.Builder()
.protocol(Protocol.HTTP_2)
// This is essential as it makes response.isSuccessful() returning true.
.code(200)
.request(chain.request())
.body(new ResponseBody() {
#Override
public MediaType contentType() {
return null;
}
#Override
public long contentLength() {
// Means we don't know the length beforehand.
return -1;
}
#Override
public BufferedSource source() {
try {
return new Buffer().readFrom(stream);
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
})
.build();
}
});
FooApi api = new FooApi(httpClient);
api.fetchData();
// TODO: Let's assert the data here.
}
}
This is now even easier with Mockinizer which makes working with MockWebServer easier:
val mocks: Map<RequestFilter, MockResponse> = mapOf(
RequestFilter("/mocked") to MockResponse().apply {
setResponseCode(200)
setBody("""{"title": "Banana Mock"}""")
},
RequestFilter("/mockedError") to MockResponse().apply {
setResponseCode(400)
}
)
Just create a map of RequestFilter and MockResponses and then plug it into your OkHttpClient builder chain:
OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.mockinize(mocks) // <-- just plug in your custom mocks here
.build()
I recently started to use Volley lib from Google for my network requests. One of my requests get error 301 for redirect, so my question is that can volley handle redirect somehow automatically or do I have to handle it manually in parseNetworkError or use some kind of RetryPolicyhere?
Thanks.
Replace your url like that url.replace("http", "https");
for example:
if your url looking like that : "http://graph.facebook......." than
it should be like : "https://graph.facebook......."
it works for me
I fixed it catching the http status 301 or 302, reading redirect url and setting it to request then throwing expection which triggers retry.
Edit: Here are the main keys in volley lib which i modified:
Added method public void setUrl(final String url) for class Request
In class BasicNetwork is added check for redirection after // Handle cache validation, if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY) || statusCode == HttpStatus.SC_MOVED_TEMPORARILY), there I read the redirect url with responseHeaders.get("location"), call setUrl with request object and throw error
Error get's catched and it calls attemptRetryOnException
You also need to have RetryPolicy set for the Request (see DefaultRetryPolicy for this)
If you dont want to modify the Volley lib you can catch the 301 and manually re-send the request.
In your GsonRequest class implement deliverError and create a new Request object with the new Location url from the header and insert that to the request queue.
Something like this:
#Override
public void deliverError(final VolleyError error) {
Log.d(TAG, "deliverError");
final int status = error.networkResponse.statusCode;
// Handle 30x
if(HttpURLConnection.HTTP_MOVED_PERM == status || status == HttpURLConnection.HTTP_MOVED_TEMP || status == HttpURLConnection.HTTP_SEE_OTHER) {
final String location = error.networkResponse.headers.get("Location");
Log.d(TAG, "Location: " + location);
final GsonRequest<T> request = new GsonRequest<T>(method, location, jsonRequest, this.requestContentType, this.clazz, this.ttl, this.listener, this.errorListener);
// Construct a request clone and change the url to redirect location.
RequestManager.getRequestQueue().add(request);
}
}
This way you can keep updating Volley and not have to worry about things breaking.
Like many others, I was simply confused about why Volley wasn't following redirects automatically. By looking at the source code I found that while Volley will set the redirect URL correctly on its own, it won't actually follow it unless the request's retry policy specifies to "retry" at least once. Inexplicably, the default retry policy sets maxNumRetries to 0. So the fix is to set a retry policy with 1 retry (10s timeout and 1x back-off copied from default):
request.setRetryPolicy(new DefaultRetryPolicy(10000, 1, 1.0f))
For reference, here is the source code:
/**
* Constructs a new retry policy.
* #param initialTimeoutMs The initial timeout for the policy.
* #param maxNumRetries The maximum number of retries.
* #param backoffMultiplier Backoff multiplier for the policy.
*/
public DefaultRetryPolicy(int initialTimeoutMs, int maxNumRetries, float backoffMultiplier) {
mCurrentTimeoutMs = initialTimeoutMs;
mMaxNumRetries = maxNumRetries;
mBackoffMultiplier = backoffMultiplier;
}
Alternatively, you can create a custom implementation of RetryPolicy that only "retries" in the event of a 301 or 302.
Hope this helps someone!
End up doing a merge of what most #niko and #slott answered:
// Request impl class
// ...
#Override
public void deliverError(VolleyError error) {
super.deliverError(error);
Log.e(TAG, error.getMessage(), error);
final int status = error.networkResponse.statusCode;
// Handle 30x
if (status == HttpURLConnection.HTTP_MOVED_PERM ||
status == HttpURLConnection.HTTP_MOVED_TEMP ||
status == HttpURLConnection.HTTP_SEE_OTHER) {
final String location = error.networkResponse.headers.get("Location");
if (BuildConfig.DEBUG) {
Log.d(TAG, "Location: " + location);
}
// TODO: create new request with new location
// TODO: enqueue new request
}
}
#Override
public String getUrl() {
String url = super.getUrl();
if (!url.startsWith("http://") && !url.startsWith("https://")) {
url = "http://" + url; // use http by default
}
return url;
}
It worked well overriding StringRequest methods.
Hope it can help someone.
Volley supports redirection without any patches, no need for a separate fork
Explanation:
Volley internally uses HttpClient which by default follows 301/302 unless specified otherwise
From: http://hc.apache.org/httpcomponents-client-4.2.x/tutorial/html/httpagent.html
ClientPNames.HANDLE_REDIRECTS='http.protocol.handle-redirects': defines whether redirects should be handled automatically. This parameter expects a value of type java.lang.Boolean. If this parameter is not set HttpClient will handle redirects automatically.
ok, im a bit late to the game here, but i've recently been trying to achieve this same aspect, so https://stackoverflow.com/a/17483037/2423312 is the best one, given that you are willing to fork volley and maintain it and the answer here : https://stackoverflow.com/a/27566737/2423312 - I'm not sure how this even worked.This one is spot on though : https://stackoverflow.com/a/28454312/2423312. But its actually adding a new request object to the NetworkDipatcher's queue, so you'll have to notify the caller as well somehow, there is one dirty way where you can do this by not modifying the request object + changing the field "mURL", PLEASE NOTE THAT THIS IS DEPENDENT ON YOUR IMPLEMENTATION OF VOLLEY'S RetryPolicy.java INTERFACE AND HOW YOUR CLASSES EXTENDING Request.java CLASS ARE, here you go : welcome REFLECTION
Class volleyRequestClass = request.getClass().getSuperclass();
Field urlField = volleyRequestClass.getDeclaredField("mUrl");
urlField.setAccessible(true);
urlField.set(request, newRedirectURL);
Personally I'd prefer cloning volley though. Plus looks like volley's example BasicNetwork class was designed to fail at redirects : https://github.com/google/volley/blob/ddfb86659df59e7293df9277da216d73c34aa800/src/test/java/com/android/volley/toolbox/BasicNetworkTest.java#L156 so i guess they arent leaning too much on redirects, feel free to suggest/edit. Always looking for good way..
I am using volley:1.1.1 with https url though the request was having some issue. On digging deeper i found that my request method was getting changed from POST to GET due to redirect (permanent redirect 301). I am using using nginx and in server block i was having a rewrite rule that was causing the issue.
So in short everything seems good with latest version of volley. My utility function here-
public void makePostRequest(String url, JSONObject body, final AjaxCallback ajaxCallback) {
try {
JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(Request.Method.POST,
url, body, new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
Log.d(LOG, response.toString());
ajaxCallback.onSuccess(response);
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.e(LOG, error.toString());
ajaxCallback.onError(error);
}
});
singleton.getRequestQueue().add(jsonObjectRequest);
} catch(Exception e) {
Log.d(LOG, "Exception makePostRequest");
e.printStackTrace();
}
}
// separate file
public interface AjaxCallback {
void onSuccess(JSONObject response);
void onError(VolleyError error);
}
I am having is issues in obtaining the public access token for my app. I am getting the following error:
05-26 14:43:17.194: D/Mobli(1219): Response {"error":"invalid_request","error_description":"The request includes an unsupported parameters","error_uri":"http://dev.mobli.com/error/invalid_request"}
The code that I am using to make the request is as follows:
Mobli mobli = new Mobli(ID, SECRET);
SampleRequestListener mobliListner = new SampleRequestListener();
runner = new AsyncMobliRunner(mobli);
runner.obtainPublicToken(mobliListner, null);
public class SampleRequestListener extends BaseRequestListner {
public void onComplete(final String response, final Object state) {
try {
// process the response here: executed in background thread
Log.d("Mobli", "Response " + response.toString());
} catch (MobliError e) {
Log.w("Mobli Error", "Error" + e.getMessage());
}
}
}
Any idea what might be wrong with the code?
I have also verified that the URL is formed correctly. I am getting the filenotfoundexcetion in util.java
Turns out there was as an issue in openUrl function in util.java that is a part of mobli sdk. In the openUrl function an extra parameter was being appended to the post request, which was resulting in the above error. Specifically, commenting out the following lines in openUrl function solved the above issue.
// use method override
if (!params.containsKey("method")) {
params.putString("method", method);
}