Most Android apps use a networking client to talk to some API. The most popular 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 that lists all the endpoints we want to call from API.
It is passed to the 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 that 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 the 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 the 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 the @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 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 the getUser
call, you’ll get a response defined with smith.json resource. The second time you trigger the 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 behavior. 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.