Kotlin の Object を Mockito + PowerMock でモックする


この記事では、Kotlin の Object という機能を使った部分をモックする手法について説明しています。

TL; DR

Kotlin とは ?

Kotlin (ことりん) は IntelliJ IDEA で有名な JetBrains 社が開発している Java との相互運用性を持ったプログラミング言語です。

Kotlin の Object とは ?

Singleton パターン を簡潔に記述することができる記法です。明示的にインスタンスを生成するコードなどを書かずに Singleton が実現できるため、定形コードを削減することができます。

内部的な実装は、Java で Singleton を実装した場合とほとんど変わりません。

テスト対象のクラス

モックしたい Object

foo メソッドと bar プロパティが存在する Foo オブジェクトです。

Kotlin のプロパティである bar は Java でいう getBarsetBar に対応する getter/setter を裏で生成します。

Foo.kt
object Foo {
    fun foo(): String {
        return "foo"
    }

    val bar: String by lazy { "bar" }
}

モック対象 Object を使ってるクラス

object で宣言したものは、Singleton として直接呼び出せます。今回は、この Bar クラスをテストする時に Foo クラスをモックしテストする話をします。

Bar.kt
class Bar {
    fun foo(): String {
        return Foo.foo()
    }

    val bar: String
        get() = Foo.bar
}

テストの際に使うライブラリ

  • Mockito
    使いやすい Java のモックライブラリです。
  • PowerMock
    Mockito だとモックできないものもモックできる、とても強い Java のモックライブラリです。

テストコード

以下で解説しているコードは、サンプルプロジェクトして GitHub 上に公開されていますので、実際に動かしてみる場合はそちらをご利用ください。

Foo クラスに対するテスト

普通に JUnit と kotlin.test を使って書きます。

FooTest.kt
import org.junit.Test
import kotlin.test.assertEquals

class FooTest {
    @Test
    fun foo() {
        assertEquals("foo", Foo.foo())
    }

    @Test
    fun bar() {
        assertEquals("bar", Foo.bar)
    }
}

Bar クラスに対するテスト

前準備として、Foo クラスをモックでテスト可能にするために @JvmStatic アノテーションを追加します。これにより、Foo クラスに対しての static メンバとしてコードが生成されます (詳細後述)。

Foo.kt
object Foo {
    @JvmStatic
    fun foo(): String {
        return "foo"
    }

    @JvmStatic
    val bar: String by lazy { "bar" }
}

Mockito と PowerMock を合わせて使います。PowerMock には Mockito 向けのインターフェイスが用意されているので、それを用います。

BarTest.kt
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito
import org.powermock.api.mockito.PowerMockito
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.PowerMockRunner
import kotlin.test.assertEquals

@RunWith(PowerMockRunner::class)
@PrepareForTest(Foo::class)
class BarTest {
    @Test
    fun foo() {
        // Foo.foo() のモック処理
        PowerMockito.mockStatic(Foo::class.java)
        Mockito.`when`(Foo.foo()).thenReturn("xxx_foo")

        // モックを用いて Bar クラスをテスト
        assertEquals("xxx_foo", Bar().foo())

        // モックが呼ばれたことを検証
        PowerMockito.verifyStatic()
        Foo.foo()
    }

    @Test
    fun bar() {
        // Foo.getBar() のモック処理
        // (Kotlin のプロパティは、Java の getter/setter に該当)
        PowerMockito.mockStatic(Foo::class.java)
        Mockito.`when`(Foo.bar).thenReturn("xxx_bar")

        assertEquals("xxx_bar", Bar().bar)

        // モックが呼ばれたことを検証
        PowerMockito.verifyStatic()
        Foo.bar
    }
}

補足

@RunWith(PowerMockRunner::class)

このテストクラスを PowerMock を使って実行することを指定します。

@PrepareForTest(Foo::class)

PowerMock で弄るクラスを指定します。PowerMock でモック化するには、事前にここで指定する必要があります。

PowerMockito.mockStatic(Foo::class.java)

クラスの static メンバをモック可能にします。このメソッドを実行後に Mockito で弄ることができるようになります。

PowerMockito.verifyStatic()

static メンバが呼ばれたかどうか検証を行います。このメソッドを実行後、モックのメソッドを期待する引数で呼び出すことで検証されます。

@JvmStatic アノテーションの働き

Kotlin の Object は、コンパイルすると Singleton パターンのクラスとなります。例えば、以下の Example.kt をコンパイルすると、

Example.kt
object Example {
    fun foo(): String {
        return "foo"
    }
}

次のようなクラスが生成されます。

public final class Example
{

    public final String foo()
    {
        return "foo";
    }

    private Example()
    {
    }

    public static final Example INSTANCE = (Example)this;
    /**
     * @deprecated Field INSTANCE$ is deprecated
     */
    public static final Example INSTANCE$ = (Example)this;

    static
    {
        new Example();
    }
}

Kotlin で Example.foo() と記述した場合に実際に呼び出されるのは Example.INSTANCE.foo() となります。

この状況で Example をモックするには、INSTANCE 変数に再度モックしたインスタンスを設定してあげる必要がありますので、モックする手間が増えます (モックは可能ですが)。

@JvmStatic アノテーションを付加した場合、以下のように foostatic メソッドしてコンパイルされます。

public final class Example
{

    public static final String foo()
    {
        return "foo";
    }

    private Example()
    {
    }

    public static final Example INSTANCE = (Example)this;
    /**
     * @deprecated Field INSTANCE$ is deprecated
     */
    public static final Example INSTANCE$ = (Example)this;

    static
    {
        new Example();
    }
}

この場合、Kotlin からは直接 static メソッドが呼ばれるようにコンパイルされますので、対象の static メソッドをモックしてあげるだけで OK です。