Get your copy of the book

Transforming the Purchasing Experience

Download
Ebook Retail Transformation Technology

Introducing Retromock, a Better Way to Mock Retrofit API Calls

  —  
 read

Most Android apps use a networking client to talk to some API. The most popular such client is probably Retrofit, a type-safe HTTP client for Android and Java.

If you are familiar with Retrofit and its usage, keep on reading to learn a better way to mock API calls.

Why should we mock API calls?

In the snippet below, we have defined a Service interface which lists all the endpoints we want to call from API.

It is passed to Retrofit builder, it generates code for GET etc. You know the drill.

public interface Service {
 @GET("/")
 Call<User> getUser();
}

However, often when working on a project in collaboration with a backend team, you may encounter some difficulties that can affect the further development process.

Some of those problems are API-related issues such as delayed API development, often API changes, or current API inactivity.

At the same time, the ability to check whether your code covers all the possible responses or how your app behaves in case of different response duration can significantly improve the development process.

Retromock is a Java library for mocking responses in a Retrofit service that aims to cover as many potentially problematic cases in the future as possible.

Retromock to the rescue

Retromock adapts a Java interface created by Retrofit using annotations on declared methods to define response mocks.

Setting it up is very simple and can be done in the following steps:

  • Add dependency
implementation "co.infinum:retromock:1.1.0"
  • Initialize
Retromock retromock = new Retromock.Builder()
 .retrofit(retrofit)
 .build();
  • Create a service class
Service service = retromock.create(Service.class);

Recipes

In the following chapters, you'll find the list of recipes which shows how to solve common problems with Retromock.

Feel free to clone the project and try them out. Make sure that you understand how they work.

Mock simple response

Let your API service implement getUser call. Keep in mind that in the following example there are no mocks yet. Mocks will be defined using annotations, as explained later.

public interface Service {
 @GET("/")
 Call<User> getUser();
}

And your data class provided by getUser call looks like this:

class User {

  @Json(name = "name")
  String name;

  @Json(name = "surname")
  String surname;
}

In this case, we use Moshi for serialization. It's okay to use another serializer to configure Retrofit, because Retromock uses the same serializer as Retrofit under the hood. This means that a fully configured Retrofit instance is required for Retromock to work.

With Retromock, there is an easy way to mock a response using @Mock and @MockResponse annotations. @Mock annotation is used to specify that the method should be mocked. @MockResponse annotation is used to specify parameters for defining a mocked response.

You can find more details in the specification.

public interface Service {
 @Mock
 @MockResponse(body = "{\"name\":\"John\", \"surname\":\"Smith\"}")
 @GET("/")
 Call<User> getUser();
}

Mock complex response

Usually, the response is too complicated to fit nicely as an annotation parameter. With Retromock, it is possible to mock responses using JSON files. Here's how to configure it to read responses from the resources directory.

smith.json

{
 "name": "John",
 "surname": "Smith"
}

There are two steps to take in order to use this JSON resource file. First, we need to set defaultBodyFactory when initializing Retromock.

Use the following implementation to read JSON files from the resources directory:

final class ResourceBodyFactory implements BodyFactory {

 @Override
 public InputStream create(final String input) throws IOException {
   return new FileInputStream(
     ResourceBodyFactory.class.getClassLoader().getResource(input).getFile()
   );
 }
}

Basically, it defines where the JSON resource is located so that Retromock can read it. DefaultBodyFactory needs to be set. By doing that, Retromock will by default read the responses using that class.

In our example, it means the response body will be read from the file in the resource directory, which is called the same as the value of the body parameter of @MockResponse annotation set.

val retromock: Retromock = Retromock.Builder()
       .retrofit(retrofit)
       .defaultBodyFactory(ResourceBodyFactory())
       .build()

Next, we need to reference the wanted response from the annotation.

@Mock
@MockResponse(body = "smith.json")
@GET("/")
Call<User> getUser();

If you want to keep both options – simple responses, and responses from the resource, you can use the parameter bodyFactory from the @MockResponse annotation.

@MockResponse(bodyFactory = ResourceBodyFactory::class, body = "smith.json")

Mock Headers

With Retromock, it is also possible to mock response headers. Take a look at the following example.

public interface Service {

 @Mock
 @MockResponse(body = "smith.json", headers = {
   @MockHeader(name = "customHeader", value = "user Smith"),
   @MockHeader(name = "Server", value = "google.com")
 })
 @GET("/")
 Call<User> getUser();
}

Mock response code

Response code can be set using the parameter code:

@Mock
@MockResponse(code = 400)
@GET("/")
Call<User> getOtherUser();

Mock with body factory not defined

If you set a custom default body factory and do not declare a bodyFactory parameter in @MockResponse annotation, your body factory will be called with the value of body parameter. That also applies if you don’t specifically set a body, like in the example above – in that case, body is empty by default.

If you prefer not to handle the case of empty body, wrap your default body factory into NonEmptyBodyFactory class as follows:

Retromock retromock = new Retromock.Builder()
 .retrofit(retrofit)
 .defaultBodyFactory(new NonEmptyBodyFactory(new ResourceBodyFactory()))
 .build();

Mock delays

When working with network calls, we should adjust our code to expect possible delays or do something after a specified timeout. With Retromock, we can prepare for both.

To mock an immediate response, initialize Retromock in the following way:

Retromock retromock = new Retromock.Builder()
 .retrofit(retrofit)
 .defaultBehavior(() -> 0)
 .defaultBodyFactory(new ResourceBodyFactory())
 .build();

defaultBehaviour(()->0) will initialize behavior that waits 0ms before returning a mocked response.

We can achieve the same thing using MockBehaviour annotation:

@MockBehavior(durationMillis = 0, durationDeviation = 0)

Mocking delays can be done by setting durationMillis and durationDeviation parameters.

durationMillis defines a delay value in milliseconds and durationDeviation defines a deviation from that value. Basically, those two parameters define a delay range of durationMillis+-durationDeviation.

Here's an example:

@MockBehavior(durationMillis = 1000, durationDeviation = 500)

This would produce a random delay in range [500, 1500>.

Compatibility with coroutines

Since Retrofit is adjusted to support coroutines for asynchronous calls, similar adjustments are done to Retromock.

So it is possible to write something like this:

interface Service {
   @GET("/")
   @MockResponse(body = "smith.json")
   @Mock
   suspend fun getCoroutineUser(): User?

   @GET("/")
   @MockResponse(body = "smith.json")
   @Mock
   suspend fun getCoroutineUserWithResponse(): Response<User>
}

Mock multiple responses

Sometimes, two consecutive calls can give two different responses, either with different data or completely different. One such instance is doing a search for some user with different filters.

We can easily mock such a behavior simply by defining multiple mock response annotations.

public interface Service {

 @Mock
 @MockResponse(body = "smith.json")
 @MockResponse(body = "doe.json")
 @GET("/")
 Call<User> getUser();
}

That's all the set up there is.

The first time when you trigger getUser call, you’ll get a response defined with smith.json resource. The second time you trigger getUser call, you’ll get a response defined with doe.json.

Additionally, there is a way to mock an indefinite number of responses in three ways: default, circular, or random. By default, mocked calls will always return the last response, in this case doe.json.

Let’s use an example with smith.json and doe.json to explain circular and random behaviour. Also, we’ll call getUser 5 times to show the result.

@MockCircular

Smith.json
Doe.json
Smith.json
Doe.json
Smith.json

@MockRandom – one possible outcome. As the name says, responses are chosen at random.

Smith.json
Doe.json
Doe.json
Smith.json
Smith.json

Mock responses with similar JSON properties

Let’s say we use our "{\"name\":\"John\", \"surname\":\"Smith\"}" as a response body. We also want to mock multiple responses, but with different first and last names. Everything else in possibly a huge response mock should stay the same.

For those cases, we can implement custom BodyFactory.

class CustomBuildBodyFactory implements BodyFactory {

 @Override
 public InputStream create(final String input) throws IOException {
   String[] args = input.split("\\.");
   return new ByteArrayInputStream(
     ("{\"name\":\"" + args[0] + "\", \"surname\":\"" + args[1] + "\"}")
       .getBytes(StandardCharsets.UTF_8)
   );
 }
}
public interface Service {

 @Mock
 @MockResponse(
   body = "John.Doe",
   bodyFactory = CustomBuildBodyFactory.class
 )

 @GET("/")
 Call<SpecificBodyFactory.User> userMockedDirectlyInAnnotation();
}

Complete control of mocked API with Retromock

There are many use cases where you can use Retromock to mock Retrofit calls. With possibilities from mocking simple or complex responses, headers or bodies to delays or multiple responses and more, Retromock provides complete control of the mocked API.

Most used mock recipes are explained in this article, but feel free to check out the specification and the source code for more details.

Nikolina Fuzul gave us major zen vibes with the cover illustration.