Unit testing with Android volley
Asked Answered
A

4

45

I would like to know how can I create unit tests for the volley framework. Mock the requests, the responses so that I can create unit tests that wont require a webservice to be working and network accesss.

I've googled it but I don't find much information about the framework at all

Albuminoid answered 4/9, 2013 at 15:54 Comment(2)
Currently there's zero document I could find. But there's plenty of sample and Google IO 2013 video is also easy to dig in.Carob
For others that land here, consider using OkHttp instead of Volley. It's a more modern framework that can be much more easily tested using OkHttp's MockWebServer.Llanes
O
27

I implemented a subclass of HttpStack named FakeHttpStack that load fake response body from local file located in res/raw. I did this for development purpose, i.e., I can develop something for new API before server is ready, but you may learn something (e.g., overriding HttpStack#peformRequest and createEntity) from here.

/**
 * Fake {@link HttpStack} that returns the fake content using resource file in res/raw.
 */
class FakeHttpStack implements HttpStack {
    private static final String DEFAULT_STRING_RESPONSE = "Hello";
    private static final String DEFAULT_JSON_RESPONSE = " {\"a\":1,\"b\":2,\"c\":3}";
    private static final String URL_PREFIX = "http://example.com/";
    private static final String LOGGER_TAG = "STACK_OVER_FLOW";

    private static final int SIMULATED_DELAY_MS = 500;
    private final Context context;

    FakeHttpStack(Context context) {
        this.context = context;
    }

    @Override
    public HttpResponse performRequest(Request<?> request, Map<String, String> stringStringMap)
            throws IOException, AuthFailureError {
        try {
            Thread.sleep(SIMULATED_DELAY_MS);
        } catch (InterruptedException e) {
        }
        HttpResponse response
                = new BasicHttpResponse(new BasicStatusLine(HttpVersion.HTTP_1_1, 200, "OK"));
        List<Header> headers = defaultHeaders();
        response.setHeaders(headers.toArray(new Header[0]));
        response.setLocale(Locale.JAPAN);
        response.setEntity(createEntity(request));
        return response;
    }

    private List<Header> defaultHeaders() {
        DateFormat dateFormat = new SimpleDateFormat("EEE, dd mmm yyyy HH:mm:ss zzz");
        return Lists.<Header>newArrayList(
                new BasicHeader("Date", dateFormat.format(new Date())),
                new BasicHeader("Server",
                        /* Data below is header info of my server */
                        "Apache/1.3.42 (Unix) mod_ssl/2.8.31 OpenSSL/0.9.8e")
        );
    }

    /**
     * returns the fake content using resource file in res/raw. fake_res_foo.txt is used for
     * request to http://example.com/foo
     */
    private HttpEntity createEntity(Request request) throws UnsupportedEncodingException {
        String resourceName = constructFakeResponseFileName(request);
        int resourceId = context.getResources().getIdentifier(
                resourceName, "raw", context.getApplicationContext().getPackageName());
        if (resourceId == 0) {
            Log.w(LOGGER_TAG, "No fake file named " + resourceName
                    + " found. default fake response should be used.");
        } else {
            InputStream stream = context.getResources().openRawResource(resourceId);
            try {
                String string = CharStreams.toString(new InputStreamReader(stream, Charsets.UTF_8));
                return new StringEntity(string);
            } catch (IOException e) {
                Log.e(LOGGER_TAG, "error reading " + resourceName, e);
            }
        }

        // Return default value since no fake file exists for given URL.
        if (request instanceof StringRequest) {
            return new StringEntity(DEFAULT_STRING_RESPONSE);
        }
        return new StringEntity(DEFAULT_JSON_RESPONSE);
    }

    /**
     * Map request URL to fake file name
     */
    private String constructFakeResponseFileName(Request request) {
        String reqUrl = request.getUrl();
        String apiName = reqUrl.substring(URL_PREFIX.length());
        return "fake_res_" + apiName;
    }
}

To use FakeHttpStack, you just have to pass it to your RequestQueue. I override RequestQueue too.

public class FakeRequestQueue extends RequestQueue {
    public FakeRequestQueue(Context context) {
        super(new NoCache(), new BasicNetwork(new FakeHttpStack(context)));
    }
}

Good point for this approach is that, it doesn't require much change in your code. You just have to switch your RequestQueue to FakeRequestQueue when testing. Thus, it can be used in acceptance testing or system testing.

On the other hand, for unit testing, there might be more compact way. For instance, you can implement your Request.Listener subclass as separate class so that onResponse method can be easily tested. I recommend you to put more detail about what you want to test or put some code fragment.

Ornithorhynchus answered 30/11, 2013 at 6:59 Comment(4)
You can extend Volley.java and override newRequestQueue() to give the FakeRequestQueue. If you use dependency injection you can simply inject your MyVolley.java instead of the default Volley.java so your app code does not require any changes for testing.Ewaewald
Can you guide me how to mock response for unit testing volley request.Fallow
Hello, i have implemented fakeRequestQueue, but it is not giving any response. Have a look here #32623629Fallow
Great solution. I didn't override RequestQueue, but it still works great for me. Thanks a lot.Nan
M
7

Take a look at volley tests folder, there you can find examples.

MockCache.java
MockHttpClient.java
MockHttpStack.java
MockHttpURLConnection.java
MockNetwork.java
MockRequest.java
MockResponseDelivery.java
Milksop answered 22/11, 2013 at 12:43 Comment(0)
W
0

Not 100% sure that I understand what you want to do, but if I do, then easymock (a lib that allows the creation of mock classes, that you can make calls to and receive predetermined responses) will help you out a lot. A guy called Lars Vogel has a nice article on this topic that I found useful a while back when I used it.

http://www.vogella.com/articles/EasyMock/article.html

Weatherworn answered 7/9, 2013 at 12:53 Comment(0)
H
0

Here's a copy of the current volley's MockHttpStack mentioned by @Dmytro

package com.android.volley.mock;
import com.android.volley.AuthFailureError;
import com.android.volley.Request;
import com.android.volley.toolbox.HttpStack;
import org.apache.http.HttpResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
public class MockHttpStack implements HttpStack {
    private HttpResponse mResponseToReturn;
    private IOException mExceptionToThrow;
    private String mLastUrl;
    private Map<String, String> mLastHeaders;
    private byte[] mLastPostBody;
    public String getLastUrl() {
        return mLastUrl;
    }
    public Map<String, String> getLastHeaders() {
        return mLastHeaders;
    }
    public byte[] getLastPostBody() {
        return mLastPostBody;
    }
    public void setResponseToReturn(HttpResponse response) {
        mResponseToReturn = response;
    }
    public void setExceptionToThrow(IOException exception) {
        mExceptionToThrow = exception;
    }
    @Override
    public HttpResponse performRequest(Request<?> request, Map<String, String> additionalHeaders)
            throws IOException, AuthFailureError {
        if (mExceptionToThrow != null) {
            throw mExceptionToThrow;
        }
        mLastUrl = request.getUrl();
        mLastHeaders = new HashMap<String, String>();
        if (request.getHeaders() != null) {
            mLastHeaders.putAll(request.getHeaders());
        }
        if (additionalHeaders != null) {
            mLastHeaders.putAll(additionalHeaders);
        }
        try {
            mLastPostBody = request.getBody();
        } catch (AuthFailureError e) {
            mLastPostBody = null;
        }
        return mResponseToReturn;
    }
}
Hyehyena answered 5/6, 2018 at 9:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.