Mock Server Errors w/ Retrofit and RxJava

Set the stage

You have an Android app that uses Retrofit for the server calls and responds using RxJava. An overly simplified example of your call might be something like this:

@GET(MYPATH)
Observable<MYPOJO> mycall();

When you call the mycall() function, it accesses MYPATH on the server, via GET, converts the results into MYPOJO and wraps it in an Observable.

Obviously, your project would use much more useful names - but I wanted to keep this generic.

For this example, I am also assuming you are using retrolambda or the newer APIs… If not, the IDE can convert to/from a generic anonymous class for you - it’s just more boilerplate.

You have a need, a desire, nay a priority ticket - to validate the app behaves in a certain way given a particular server response; let’s say a 500 Server Error.

This post is NOT going to cover the various methods for unit testing that behavior. This is strictly about how you can enforce the failure during development to make it easier for you to iterate on it. I might do a post on OkReplay or something later - but that’s not the point here.

Sure, you could fire up Charles Proxy (as long as you are not using a HiDpi monitor) and intercept the call and tweak the results.

Or we could just lie about what we received from the server.

The Code

Direct Calls

One way we could do this would be to intercept that specific call. It’s not very reusable, but it’s a start.

from Java

import retrofit2.HttpException;
import retrofit2.Response;
import okhttp3.ResponseBody;
import okhttp3.MediaType;
...
myApi.mycall()
    .flatMap(resp -> throw new HttpException(Response.error(500, ResponseBody.create(MediaType.parse("text/plain"), "Test Server Error"))));

You can see here that we are specifying the status code, the media type and the message itself. We can make that line a little shorter by changing to static imports, but I was trying to be clear in what it is doing. No matter what the server responds with (assuming success), we are going to throw an HttpException in response. RxJava will, in turn, pass it to the onError in your code - which you can then use to validate that your app behaves as expected.

You would never check this code in (without a feature flag) but it can greatly speed up development time to test that aforementioned ticket.

from Kotlin

I know some of you would rather be using Kotlin.

import retrofit2.HttpException
import retrofit2.Response
import okhttp3.ResponseBody
import okhttp3.MediaType
...
myApi.mycall()
    .flatMap<Any> { resp -> throw HttpException(Response.error<Any>(500, ResponseBody.create(MediaType.parse("text/plain"), "Test Server Error"))) }

Java Helper

Ok, that’s nice and all… but if we want to use it more than once, surely we can make that a bit nicer?

This is just a generic class. You could make it better using a builder pattern or maybe requiring specific parameters… For our purposes, we are going to keep it simple.

import okhttp3.MediaType;
import okhttp3.ResponseBody;
import retrofit2.HttpException;
import retrofit2.Response;
import rx.Observable;
import rx.functions.Func1;

public class MockServerError<T> implements Func1<T, Observable<? extends T>> {
    private static final int DEFAULT_CODE = 500;
    private static final String DEFAULT_MESSAGE = "Mock Server Error";
    private static final String DEFAULT_MIME = "text/plain";

    private int code;
    private String msg;
    private String mime;

    public MockServerError() {
        this(DEFAULT_CODE, DEFAULT_MIME, DEFAULT_MESSAGE);
    }

    public MockServerError(int code) {
        this(code, DEFAULT_MIME, DEFAULT_MESSAGE);
    }

    public MockServerError(int code, String msg) {
        this(code, DEFAULT_MIME, msg);
    }

    public MockServerError(int code, String mimeType, String msg) {
        this.code = code;
        this.mime = mimeType;
        this.msg = msg;
    }

    @Override
    public Observable<? extends T> call(T t) {
        throw new HttpException(Response.error(code, ResponseBody.create(MediaType.parse(mime), msg)));
    }
}

It doesn’t do anything special. It’s the same class we did inline - it just lets us construct it with various parameters.

from Java

Calling this class from Java is as you would expect

myApi.mycall()
    .flatMap(new MockServerError<>());

Of course, you could choose to pass some parameters to change it to 404 or something.

from Kotlin

myApi.mycall()
    .flatMap(MockServerError<Any>())

Lose the new, add the Any. Not much different.

Kotlin Helper

Those in the Kotlin world probably won’t want a Java Helper…

import okhttp3.MediaType
import okhttp3.ResponseBody
import retrofit2.HttpException
import retrofit2.Response
import rx.Observable
import rx.functions.Func1

class MockServerError<T>
@JvmOverloads constructor(private val code: Int = DEFAULT_CODE, private val msg: String = DEFAULT_MESSAGE, private val mime: String = DEFAULT_MIME) : Func1<T, Observable<out T>> {

    override fun call(t: T): Observable<out T> {
        throw HttpException(Response.error<Any>(code, ResponseBody.create(MediaType.parse(mime), msg)))
    }

    companion object {
        private val DEFAULT_CODE = 500
        private val DEFAULT_MESSAGE = "Mock Server Error"
        private val DEFAULT_MIME = "text/plain"
    }
}

Obviously, much shorter. What might not be obvious at first glance is that I changed the order of the parameters. The reason for that was calling the class from Java gave us 3 constructors - the 1st param, the 1st and 2nd, or all 3. When mime type was second, we could not provide a message without also providing a mime type.

from Java

This can, of course, look exactly the same:

myApi.mycall()
    .flatMap(new MockServerError<>());

Or maybe with some parameters…

myApi.mycall()
    .flatMap(new MockServerError<>(404, "Fake Error Message", "text/plain"));

As mentioned above, I had to change the order to make the constructors match.

from Kotlin

Again, it can look the same regardless of which helper you use.

myApi.mycall()
    .flatMap(MockServerError<Any>())

If we would like some parameters…

myApi.mycall()
    .flatMap(MockServerError<Any>(code = 404, mime = "text/plain", msg = "Fake Server Error"))

Order doesn’t matter when calling Kotlin->Kotlin.

Summary

As you can see, it’s pretty trivial regardless of whether you are using Java, Kotlin or a mix of both. You can easily pretend like the server returned unexpected results and then handle it. Maybe instead of text/plain, you will pass back some jsonp or maybe you’ll return some xml when the app is expecting json. It’s really up to you.


© 2019. All rights reserved.