アプリ開発はそのアプリで閉じるということは希で、通常バックエンドとしてのサーバーサイドの開発も必要です。開発初期段階ではテスト的にたたけるAPIが無かったりで、アプリの開発を始めにくい場合もあります。
OkHttpにはInterceptorというリクエストをフックする仕組みがあるため、ここで細工をすることで、特定のURLなどへのアクセスを止めて、代わりに埋め込みのダミー応答を返すようにすることができます。
OkHttpClientのインスタンスはプールされていると思うので、ここで細工をしておけば、他の処理は本番かダミー応答かを気にせず、実装を行うことができます。Retrofitを使う場合も、OkHttpClientのインスタンスを渡すだけでOKですね。
よくある使い方でもあると思うので、ほとんど自分用メモになってしまうかもしれませんが、説明します。
Interceptor
OkHttpではNetworkInterceptorとInterceptorの2種類があって、これの違いがたまに話題になります。NetworkInterceptorはCacheの後の本当のネットワークへのリクエストの処理を行う場合に使用します。今回は、アプリケーションからのリクエストを差し替えるので、Interceptorを利用します。
Interceptorの実装
Interceptorを実装します。
どのようなリクエストを何に置き換えるのか、URLを受け取ってそれを元に変換するラムダを複数登録削除できるようにしています。クエリに応じて動的にレスポンスを作ることもできますね。全部のラムダがnullを返す場合は本来のネットワークアクセスをします。
この辺の仕組みは各アプリの事情に応じて変更すれば良いと思います。
ポイントは、chain.request()
で、リクエスト情報を読み出し、差し替えるべき応答があれば、その応答のResponseを返す。
無い場合は、そのまま本来のリクエストを実行するため、chain.proceed(request)
で、後段の処理を実行します。
class MockResponseInterceptor : Interceptor {
private val supplierMap = mutableMapOf<String, (URL) -> MockResponse?>()
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val url = request.url.toUrl()
val data = supplierMap.values
.asSequence()
.mapNotNull { it.invoke(url) }
.firstOrNull()
return data?.let {
Response.Builder()
.request(request)
.code(it.code)
.message(it.message)
.protocol(Protocol.HTTP_2)
.body(it.body.toResponseBody("application/json".toMediaType()))
.build()
} ?: chain.proceed(request)
}
fun addSupplier(key: String, supplier: (URL) -> MockResponse?) {
supplierMap[key] = supplier
}
fun removeSupplier(key: String) {
supplierMap.remove(key)
}
data class MockResponse(
val body: String,
val code: Int = 200,
val message: String = "OK"
)
}
利用
利用箇所はかなりやっつけですが、OkHttpClientを作るところでInterceptorを渡しておいて、そのclinetを使うだけです。Retrofitなど、OkHttpのHttpClientを受け付けるライブラリならこのインスタンスを渡すだけです。
val interceptor = MockResponseInterceptor()
interceptor.addSupplier("hoge") {
if (it.path.endsWith("hoge")) MockResponse("{}") else null
}
val client = OkHttpClient.Builder()
.addInterceptor(interceptor)
.build()
val request = Request.Builder()
.get()
.url("http://example.com/hoge")
.build()
GlobalScope.launch(Dispatchers.IO) {
client.newCall(request).execute().body?.string()
?.let {
Log.e("XXXX", it)
}
}
OkHttpClientはインスタンスを使い回すために、インスタンスプールを用意している場合が多いでしょうし、APIを叩いている場所ではなく、OkHttpCleintのインスタンス管理部分に細工をするだけで実現できるため、API利用箇所に変更を加えることなく、デバッグ時のみダミー応答を利用するとか、デバッグメニューによって応答を変えるなども比較的簡単に実現できると思います。
まだAPIが実装できていないとか、通常は発生しないような特定の応答を返した場合の動作確認をしたい、みたいな時に便利に使えると思います。
以上です。