【Kotlin/Android】MockKの使い方!モックの作成方法とテストコードの実装
この記事からわかること
- Android Studio/Kotlinで使えるMockKの使い方
- モックオブジェクトの作成方法
- verifyメソッドの使い方
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
環境
- Android Studio:Koala
- Kotlin:1.9.0
- JUnit:4
- MockK:1.13.13
MockKとは?
「MockK」とはKotlin製のモックライブラリです。モックとはソフトウェアテストにおいて本物のオブジェクトや機能の代わりに動作を模倣する仮のオブジェクトのことを指します。そのモックを簡単に作成・管理するため機能を提供しています。
単体(ユニット)テストにおいてモックを使用したい場面はサーバーとのAPI通信やデータベースアクセス、外部ライブラリ依存部分などになるかと思います。テスト段階では実際の疎通を行わずにダミーの値を返却するモックを作成することでそれらに依存していた部分のコードのテストを可能にすることができます。
導入方法
Android StudioでMockKを導入するためには「build.gradle(Module:app)」にtestImplementation 'io.mockk:mockk:${mockkVersion}'
を記述します。
他にもデフォルトで色々入っているようです。
dependencies {
// MockK
testImplementation "io.mockk:mockk:1.13.13"
}
最新のバージョンは公式リリースノートを確認してください。
モックを作成してテストを実装する方法
今回はテスト自体をJUnit
を使用していきます。導入や使い方に関しては以下の記事を参考にしてください。
まず最初に登場人物をまとめておきます。クラウドデータベースから取得したデータを使用してあれこれするRootViewModel
クラスをテスト対象とする場合の想定で進めていきます。
- CloudRepository:クラウドデータベースを操作するクラス
→モックを作成するクラス - RootViewModel:CloudRepositoryから取得したデータを操作するクラス
→今回のテスト対象クラス
CloudRepository
はクラウドデータベースにアクセスを行うクラスがあると仮定します。今回はこのクラスのモックを作成したい状態になります。その場合メソッド内の処理(クラウドとの通信)はどうでもよくて引数と返り値が何かだけに注目します。
class CloudRepository {
/** サーバーからユーザー情報をフェッチする */
fun fetchAll(): List<User> {
// 中の処理はどうでも良い
return fetchData
}
/** ユーザー情報を更新する */
fun updateUserInfo(name: String): Boolean {
// 中の処理はどうでも良い
return result
}
}
続いてテスト対象であるRootViewModel
はCloudRepositoryから取得したデータを使用して何かしらの操作するクラスと仮定します。わかりやすくするために処理は非常に簡素ですが実際はもっと複雑な処理をしていると考えてください。このクラスの持つ各メソッドの挙動をテストしたい場合はCloudRepository
に依存しているためこのままだとテストができません。テストを実行するためにCloudRepository
のモック(模倣オブジェクト)を作成することでRootViewModel
の処理ロジックのみにフォーカスを当ててテストすることができるようになります。
class RootViewModel(
private var cloudRepository: CloudRepository
) {
/** DBにあるユーザー数を取得する */
fun usersCount(): Int {
return cloudRepository.fetchAll().size
}
/** ユーザー情報を更新する */
fun updateUserInfo(name: String): Boolean {
return cloudRepository.updateUserInfo(name)
}
}
テストコードを作成する
では実際にRootViewModel
のテストコードクラスを実装してみます。先に全体のコードを貼っておきます。
import io.mockk.every
import io.mockk.mockk
import org.junit.Assert.assertEquals
import org.junit.BeforeClass
import org.junit.Test
class RootViewModelTest {
companion object {
private lateinit var viewModel: RootViewModel
@BeforeClass
@JvmStatic
fun setUpClass() {
// CloudRepositoryのモック(模倣オブジェクト)を作成
val mockRepository = mockk<CloudRepository>()
// モックの持つ振る舞いをセットアップ
every { mockRepository.fetchAll() } returns listOf(User(1, "ame"), User(2, "fure"))
every { mockRepository.updateUserInfo("AME") } returns true
every { mockRepository.updateUserInfo("") } returns false
// テスト用のRootViewModelをモックを渡してインスタンス化
viewModel = RootViewModel(mockRepository)
}
}
@Test
fun usersCount() {
val result = viewModel.usersCount()
assertEquals(2, result)
}
@Test
fun updateUserInfo() {
val result = viewModel.updateUserInfo("AME")
assertEquals(true, result)
val result2 = viewModel.updateUserInfo("")
assertEquals(false, result2)
}
}
ここで注目したいのがsetUpClass
メソッドの部分です。このメソッドの中でやっているのは以下のとおりです。@BeforeClass
などはJUnitのアノテーションなので割愛します。
- モックオブジェクトのベースを作成
- モックオブジェクトの各振る舞いにダミー値をセット
- テスト対象クラスにモックを渡す
1.モックオブジェクトのベースを作成
モックオブジェクト自体はmockk<クラス名>()
で作成することができます。この時点で作成されるのはあくまでベースのみで各振る舞いにはダミー値がありません。
// 1.CloudRepositoryのモック(模倣オブジェクト)を作成
val mockRepository = mockk<CloudRepository>()
2.モックオブジェクトの各振る舞いにダミー値をセット
モックオブジェクトが持つ各振る舞いにダミー値をセットするにはevery { } returns 値
を使用します。{}
ブロックで指定したメソッド(引数込み)に対しての返り値をreturns 値
で指定することができます。
// 2.モックの持つ振る舞いをセットアップ
every { mockRepository.fetchAll() } returns listOf(User(1, "ame"), User(2, "fure"))
every { mockRepository.updateUserInfo("AME") } returns true
every { mockRepository.updateUserInfo("") } returns false
3.モックオブジェクトのベースを作成
ここはMockKの関係ないところですが今回は引数で受け取る形なのでモックオブジェクトを引数に渡します。モックで作成したオブジェクトも型はCloudRepository
なので問題なく渡すことが可能です。
// 3.テスト用のRootViewModelをモックを渡してインスタンス化
viewModel = RootViewModel(mockRepository)
これで各振る舞いの定義が完了したので定義した振る舞いを使用したテストコード部分が記述、実行できるようになります。
@Test
fun usersCount() {
val result = viewModel.usersCount()
assertEquals(2, result)
}
@Test
fun updateUserInfo() {
val result = viewModel.updateUserInfo("AME")
assertEquals(true, result)
val result2 = viewModel.updateUserInfo("")
assertEquals(false, result2)
}
モックを作成した場合に引数込みでダミー値を指定する必要があるので定義していない引数を使用して参照してテストを実行するとエラーになります。
// FUREという引数でのダミー値は定義していない
val result3 = viewModel.updateUserInfo("FURE")
assertEquals(false, result3)
no answer found for CloudRepository(#1).updateUserInfo(FURE) among the configured answers: (CloudRepository(#1).fetchAll())
verifyメソッドの使い方
MockKにはモックの振る舞いの呼び出しを検証するためのverify
メソッドが用意されています。先ほどのテストクラスにverify
メソッドを導入するためにmockRepository
変数をプロパティにしておきます。
private lateinit var mockRepository: CloudRepository
@BeforeClass
@JvmStatic
fun setUpClass() {
// CloudRepositoryのモック(模倣オブジェクト)を作成
mockRepository = mockk<CloudRepository>()
// 〜〜〜〜〜〜〜〜〜〜
}
これで各テストメソッドからモックオブジェクトを参照できるようになるのでverify
メソッドを使用していきます。verify { モックオブジェクト.メソッド }
と指定することでテストケースの中で対象モックオブジェクトメソッドがちゃんと呼ばれたかどうかを検証することが可能です。今回はテストが簡素的なので無意味ですが、複雑な分岐処理が走った場合などに適切に呼ばれているかを確認することができます。
@Test
fun usersCount() {
val result = viewModel.usersCount()
assertEquals(2, result)
// verify を使用してモックが呼び出されたことを確認
verify { mockRepository.fetchAll() }
}
@Test
fun updateUserInfo() {
val result = viewModel.updateUserInfo("AME")
assertEquals(true, result)
val result2 = viewModel.updateUserInfo("")
assertEquals(false, result2)
// verify を使用してモックが正しく呼び出されたことを確認
verify { mockRepository.updateUserInfo("AME") }
verify { mockRepository.updateUserInfo("") }
// 呼び出し順序を確認したい場合
verifyOrder {
mockRepository.updateUserInfo("")
mockRepository.updateUserInfo("AME")
// 上記の順に実行されていないのでテストは失敗する
// Verification failed: calls are not in verification order
}
}
呼び出し回数を検証
// 1回呼び出されたことを検証
verify(exactly = 1) { mockRepository.fetchAll() }
// 0を指定することで呼ばれていないことも検証可能
verify(exactly = 0) { mockRepository.updateUserInfo("Unknown") }
呼び出し回数を範囲で指定
// 呼び出し回数が1〜3回の間であることを検証
verify(atLeast = 1, atMost = 3) { mockRepository.fetchAll() }
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。