Blog Infos
Author
Published
Topics
,
Published
Topics
,

This article is a mix of presenting a new tool I have developed to make network mocks easier on Retrofit, Volley and any OkHttp based tool and my learnings during the process. Let’s start by introducing Iris Mock and what is possible to do with it.

Introducing Iris Mock

Most likely, every Android developer has ever needed to mock networks calls. This may happen for several reasons, such as not relying on live apis for automated tests, a new feature that’s not yet available on api and so on. The common point for whatever reason you have imagined is: the need for creating boilerplate (and annoying) code 🤯.

This is the scenario where Iris Mock fits perfectly. You don’t have to worry about all these boilerplate codes. This will be done through a nice DSL api, where you can implement your mock logic in an idiomatic way.

Traditional way to mock calls

Let’s take a look on the traditional way to mock network calls on Retrofit:

class MockInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val uri = chain.request().url().uri().toString()
val responseString = when {
uri.endsWith("starred") -> getListOfReposBeingStarredJson
else -> ""
}
return chain.proceed(chain.request())
.newBuilder()
.code(SUCCESS_CODE)
.protocol(Protocol.HTTP_2)
.message(responseString)
.body(ResponseBody.create(MediaType.parse("application/json"), responseString.toByteArray()))
.addHeader("content-type", "application/json")
.build()
}
}
}

adapted from: https://wahibhaq.medium.com/7968e1f0d050

You also need to add the interceptor to the OkHttpClient builder:

val client = OkHttpClient.Builder()
.addInterceptor(MockInterceptor())
.build()
}
val retrofit = Retrofit.Builder()
.client(client)
.baseUrl(BASE_URL)
.build()
Using Iris Mock 😎

Now, let’s mock the same call, using Iris Mock. First, we need to add Iris Mock plugin to the app module build.gradle.kts:

plugins {
id("dev.arildo.iris-mock-plugin") version "1.1.0"
}

using older gradle? see how to configure here: https://irismock.arildo.dev/getting-started/configure-gradle/

Now, you can start to implement your interceptor:

@IrisMockInterceptor
class MockInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain) = irisMockScope(chain) {
onGet(endsWith = "starred") mockResponse getListOfReposBeingStarredJson
}
}

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

,

Blast Off_ Managing Hundreds of UI Updates for Emoji Cannons

Managing a state might be a challenge. Managing the state with hundreds of updates and constant recomposition of floating emojis is a challenge indeed.
Watch Video

Blast Off_ Managing Hundreds of UI Updates for Emoji Cannons

Piotr Prus
Android developer

Blast Off_ Managing Hundreds of UI Updates for Emoji Cannons

Piotr Prus
Android developer

Blast Off_ Managing Hundreds of UI Updates for Emoji Cannons

Piotr Prus
Android developer

Jobs

and… that’s all. But wait, are we not forgetting to add interceptor to the OkHttpClient ? Well, the answer is no. Everything you need to do is annotate your class with @IrisMockInterceptor and Iris Mock will take care of the rest for you, injecting the custom interceptors directly into the OkHttpClient bytecode.

Besides being faster, it also makes possible to use custom interceptors on where normally we was unable, like 3rd-party libraries. As these libraries usually don’t expose its OkHttpClient builder, it’s impossible to add custom interceptors. As Iris Mock doesn’t rely on this approach, you can add your interceptors everywhere, even on external libraries 🥹.

More details on official documentation: https://irismock.arildo.dev/

A more complex Iris Mock example:

@IrisMockInterceptor
class MyInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain) = irisMockScope(chain) {
onGet(contains = "user/profile") mockResponse userProfileJson
onPost(endsWith = "/login") then {
delay(2_000) // simulating a slow response
if (requestContains("validPassword")) mockResponse(successLoginJson)
else mockResponse(errorPasswordJson)
}
enableLogs() // you can also log the calls
}
}
view raw complex.kt hosted with ❤ by GitHub
Extra: Developing a plugin

The path to create a plugin involves a lot of learnings. At the end, the plugin is quite different from what I though about its implementation.

One thing I would like to achieve with Iris Mock was, somehow, remove the need to add interceptors manually on OkHttpClient. The first reason is to make its usage simple and the second one is to make possible to be used with external libraries. At this moment I knew I should work on bytecode level.

First approach: add all interceptors to OkHttpClient bytecode

The idea was simple: create an annotation class, IrisMockInterceptor, iterate over annotated classes to get its reference and add them to the OkHttpClient bytecode. The injection, by itself, is not so complex. I used the ASM library, which helps manipulate the bytecode. However, it didn’t work as expected for one reason: ASM iterates over all classes once and there’s no way to grant that we know all annotated classes when ASM is visiting OkHttpClient bytecode, so this approach wouldn’t work 🥲.

Second approach: Wrapper class using KSP

Since is impossible to predict which classes should be injected, I had another idea: What if I generate a wrapper class in buildtime, holding all annotated classes instances? It could work, because in this way, I would know the exactly class to be injected on bytecode. To generate this wrapper class, I decided to go with KSP, that’s up to 2x faster than KAPT.

And voilà. It worked. But, there was something bothering me. Now, to use Iris Mock, users would need to add KSP plugin, Iris Mock plugin, Iris Mock KSP dependency and Iris Mock KSP runtime dependency:

plugins {
id("com.google.devtools.ksp")
id("dev.arildo.iris-mock-plugin") version "1.0.1"
}
dependencies {
implementation("dev.arildo:iris-mock:1.0.1")
ksp("dev.arildo:iris-mock-compiler:1.0.1")
}
Third approach: Refactor to Kotlin Compiler Plugin

I decided to remove KSP and go with a more low level solution. Looking some other plugins, like KSP and KAPT themselves, I figured out that they are implemented using Kotlin Compiler Plugin (KCP), then I decided to also use it. Of course, as any solution, there are cons and pros, for example, simply there’s no documentation about KCP. Hundreds and more hundreds of lines without any documentation. I spent several days (and felt a little frustrated) before I found a repo that changed everything, Anvil’s repo.

In Anvil’s repo, there are very good comments and examples about KCP api usage, being really helpful. After that, it was possible to refactor all the code directly to KCP, removing the need to add KSP to the project.

For the Iris Mock runtime dependency, I found a little trick, delegate the responsibility of adding it to the project to the plugin itself:

class IrisMockGradlePlugin : Plugin<Project> {
override fun apply(target: Project) {
project.configurations.getByName("implementation").dependencies.add(
project.dependencies.create("dev.arildo:iris-mock:$PLUGIN_VERSION")
)
}
}

On this way, users don’t need to add Iris Mock runtime dependency manually. Another positive point is that it’s easer to manage which version of Iris Mock plugin is compatible with which version of Iris Mock dependency, since it’s defined by the plugin itself 😎.

That’s all. I hope that this tool and article have been useful for you and feel free to contribute to the project. As Iris Mock is still in alpha stage, there’s a big room for improvements and new features. Also, don’t forget to star the project’s repo on GitHub:

 

This article was previously published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
Automation is a key point of Software Testing once it make possible to reproduce…
READ MORE
blog
Every good Android application should be well tested to minimize the risk of error…
READ MORE
blog
In this part of the series, we will plan our first screen in Jetpack…
READ MORE
blog
We’ll be selecting a time whenever a user presses a number key. Following points…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu