Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
25
Help us understand the problem. What is going on with this article?

More than 3 years have passed since last update.

@ralph

Mockito を Kotlin で使うなら mockito-kotlin を使おう

この記事では、Mockito 自体の使い方などは書きませんのでご注意下さい。
Android向けの記述が多く含まれます。

そのまま使うと起きる問題

Mockito をそのまま Kotlin で使うと以下のようなコードになると思います。

val sharedPreferences = mock(SharedPreferences::class.java)
val editor = mock(SharedPreferences.Editor::class.java, RETURNS_DEEP_STUBS)

`when`(sharedPreferences.edit()).thenReturn(editor)
`when`(editor.commit()).thenReturn(true)
`when`(editor.putString(anyString(), anyString())).thenReturn(editor)

上のコードは、Android の SharedPreferences をモック化するコードです。
sharedPreferences.edit() で更にモック化された Editor を返し、commit()putString() を使えるようにしています。

モックオブジェクトの作成を行う、mock()メソッドはまあいいとして、モックオブジェクトの動作を決めるwhen()メソッドの部分がバッククオートで囲まれており、非常に見にくいです。

これは、Kotlin ではwhen という単語は予約語になってしまうため、バッククオートによるエスケープが必要になってしまうために起きる現象です。

もちろんこのままでも問題は無いのですが、せっかくなので今回はこのモックの作成コードの部分をより Kotlin らしくかけるライブラリ、 mockito-kotlin を使ってみたいと思います。

環境

依存の追加

Gradle や Maven など、それぞれの方法でライブラリの参照を追加します。

// testImplementation とかは環境に合わせる
testImplementation "com.nhaarman:mockito-kotlin:1.5.0"

使い方

モックオブジェクトの作成

引数でクラスを渡すのではなく、ジェネリクスで指定するようになります。

val sharedPreferences = mock<SharedPreferences> {}

第2引数以降で渡していたオプションは、引き続きメソッドの引数に記述します。

val editor = mock<SharedPreferences.Editor>(defaultAnswer = RETURNS_DEEP_STUBS) { }

withSettings()の内容も、全て引数で渡すことができます。

// 使える引数は以下(1.5.0現在)
extraInterfaces: Array<KClass<out Any>>? = null
name: String? = null
spiedInstance: Any? = null
defaultAnswer: Answer<Any>? = null
serializable: Boolean = false
serializableMode: SerializableMode? = null
verboseLogging: Boolean = false
invocationListeners: Array<InvocationListener>? = null
stubOnly: Boolean = false
useConstructor: Boolean = false
outerInstance: Any? = null
stubbing: KStubbing<T>.(T) -> Unit

モックオブジェクトの動作の指定

whenは使わず、モック作成時の関数引数 { } の内部に記述します。
on { モックするメソッド() } で動作をモックするメソッドを指定し、その後に続けて、doReturn, doAnswer, doThrowと続きます。
下のコードは、記事の初めで紹介した Mockito をそのまま使用したコードと等価です。

val editor = mock<SharedPreferences.Editor>(defaultAnswer = RETURNS_DEEP_STUBS) {
    on { commit() } doReturn true
    on { putString(anyString(), anyString()) } doReturn it
}
val sharedPreferences = mock<SharedPreferences> {
    on { edit() } doReturn editor
}

// 以下、記事のはじめに出たコード
val sharedPreferences = mock(SharedPreferences::class.java)
val editor = mock(SharedPreferences.Editor::class.java, RETURNS_DEEP_STUBS)

`when`(sharedPreferences.edit()).thenReturn(editor)
`when`(editor.commit()).thenReturn(true)
`when`(editor.putString(anyString(), anyString())).thenReturn(editor)

doReturn

続けて書いた値がモック対象のメソッドの返り値となります。
返り値がvoidまたはUnitの場合は、Unitを返せば問題ないです。

val stringMock = mock<String> {
    on { toString() } doReturn "mocked"
}
stringMock.toString() // => "mocked"

doAnswer

レシーバとしてモックしたオブジェクトを受け取り、そのメソッドが返すことになっている型を返す関数を記述します。
letみたいなものです。

val stringMock = mock<String> {
    on { toString() } doAnswer { "mocked" + it.hashCode() }
}
stringMock.toString() // => "mocked[数字]"

doThrow

続けて書いた例外を投げます。

val stringMock = mock<String> {
    on { toString() } doThrow RuntimeException()
}
stringMock.toString() // => RuntimeException

おわりに

モックするメソッドの引数の指定の部分では、Mockitoと同様にany()argThat()も使えるので、基本は困ること無いと思います。
他の記述方法などについては、GithubのWikiや、テストのコードを見るとわかりやすいので、書き方わかんねぇと思ったら見るとよいでしょう。

Wiki: https://github.com/nhaarman/mockito-kotlin/wiki/Mocking-and-verifying
テストコード: https://github.com/nhaarman/mockito-kotlin/tree/2.x/tests/src/test/kotlin/test

25
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
25
Help us understand the problem. What is going on with this article?