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:

adapted from:

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

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:

using older gradle? see how to configure here:

Now, you can start to implement your interceptor:

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:

A more complex Iris Mock example:

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:

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:

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 😎.

Source link