外部からprivateなsuspendメソッドを実行する方法
private suspendなメソッドをリフレクションで取得して非同期実行する方法。
Created at

574 Words
⚠️

プライベートメソッドのテストを書きたいときなどの方法。
Class.getDeclaredMethod(...)を使ってプライベートメソッドを取得して実行する際に気を付けなければいけないことが通常のメソッドに比して少し増える。

実行したいメソッド

たとえば以下のようなメソッドをリフレクションでぶっこ抜いてきて実行したいとする。

class Hoge {
    private suspend fun hoge(foo: Int, bar: String) = withContext(Dispachers.IO) {
        ...
    }
}

メソッドの取得方法

val hogeMethod = Hoge::class.java.getDeclaredMethod(
        "hoge",
        Int::class.java,
        String::class.java,
        Continuation::class.java
    ).apply {
        isAccessible = true
    }

引数型の最後にContinuation::class.javaを記述する必要がある。

取得したメソッドの実行方法

次のような拡張メソッドを用意しておく(とまぁ便利)。

suspend fun Method.invokeSuspend(obj: Any, vararg args: Any?) : Any? =
    suspendCoroutineUninterceptedOrReturn { cont ->
        invoke(obj, *args, cont)
    }

インスタンスと引数を渡して実行する。

hogeMethod.invokeSuspend(instance, 0, "hoge")

テストでこの処理結果を受けてassertEquals(...)とかしたい場合は、
runBlockingするなりして終了を待機しておけばいいんだろうか。
いい感じの方法が他にあれば知りたい。


参考
java - How to run suspend method via reflection? - Stack Overflow


余談

Continuation<ResultT>を作ってmethod.invoke(...)の引数に渡すことも可能と言えば可能。

val continuation = Continuation<HogeResult>(coroutineContext) { result ->
    // hogeMethodの完了後呼ばれる
    ...
}
hogeMethod.invoke(instance, 0, "hoge", continuation)

この場合hogeMethodは待機されないのでそのままではうまくテストできなくて困るのだが。

See Also