この記事では、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 を使ってみたいと思います。
環境
- Kotlin 1.2.40
- Mockito 2.18.3
- mockito-kotlin 1.5.0 https://github.com/nhaarman/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