LoginSignup
2
1

More than 5 years have passed since last update.

KotlinのObjectのインスタンスを書き換える

Last updated at Posted at 2018-09-07

まえがき

 この記事ではKotlinのObjectのインスタンスを書き換える方法を紹介しますが、本来そのようなことはしない方が良いです。書き換える必要が発生した時点で設計を見直した方が良いです。この事を念頭に置いて読んでください。

ブログにもまとめました。

方法

以下のような関数を使います。

fun changeObjectInstance(target: Any, mock: Any) {
    val kClass = Class.forName(target::class.qualifiedName).kotlin
    val field = kClass.java.fields.first()
    val modifiersField = Field::class.java!!.getDeclaredField("modifiers")
    modifiersField.isAccessible = true
    modifiersField.setInt(field, field.modifiers and Modifier.FINAL.inv())
    field.isAccessible = true
    field.set(null, mock)
}

 Objectのインスタンスを書き換えられるようにするために、リフレクションを活用してINSTANCEのfinalを外したりして無理やり書き換えてしまいます。

使用例

 これは関数の引数名にmockとあるように、テスト時にObjectのインスタンスをmockに差し替えるために使うことができます。例えば以下のようなFooオブジェクトとそれを利用するHogeクラスがあります。

object Foo {
    fun bar() = 1
}

class Hoge {
    fun huga() = Foo.bar()
}

この時、

Hoge::huga()を実行した時、Foo.bar()が1度だけ呼ばれている事

という事を確認できるテストをしたいと考えたとします。プロパティとして定義されている変数であれば、Mockitoでfiled injectionしてやれば良いのですが、Objectを使ってしまっているとそうもいきません。そこで上記のchangeObjectInstanceを使います。Objectのmockを作成して、以下のようにします。

changeObjectInstance(Foo, mockFoo)

こうする事で、Objectのインスタンスがmockに書き換えられました。あとはMockitoであれば、

val hoge = Hoge()
hoge.huga()
Mockito.verify(mockFoo,times(1)).bar()

としてやる事で、所望のテストを書くことができます。テスト実行後は他のテストに影響しないように、書き換えたインスタンスを元に戻しておきましょう。

おわりに

リフレクションを使いすぎるとコードの可読性が悪化するなど、デメリットが多数存在します。くれぐれも、用法用量を守って使ってください。冒頭にも書いたように、可能であれば設計を見直すなど、使わない方向での解決策を模索する事をお勧めします。

参考

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1