Mockとは、実際のコンポーネントの動作を模倣し、そのコンポーネントとの相互作用を検証できるようにする代替オブジェクトです。単に所定の値を返すStubとは異なり、Mockはメソッドの呼び出し、渡された引数、および呼び出し回数を記録します。Mockito (2024)によると、MockはJavaおよびKotlinプロジェクトで最も人気のあるTest Doubleタイプであり、モバイルアプリケーションのユニットテストの70%以上で使用されています。
重要ポイント
Mockは、モッキングフレームワーク(Mockito、MockK、EasyMock)によって作成されるオブジェクトで、インターフェースやクラスをシミュレートし、そのメソッドへのすべての呼び出しを記録します。開発者は期待値を設定します:メソッドXが引数Yで呼び出され、Zを返す。テスト実行後、Mockは期待値が実際の呼び出しと一致したかを検証します。
この用語はTest Doublesの演劇のメタファーに由来します:Mockは(Dummyのように)単にステージに立つだけでなく、役割を演じ、自分との相互作用が正しかったかを検証する「模倣者」です。テスト対象のコードがMockの期待するメソッドを呼び出さなかった場合、または間違った引数で呼び出した場合 — テストは期待値違反のメッセージで失敗します。
Mockはフレームワークのファクトリを通じて作成されます:mockk<MyInterface>()またはMockito.mock(MyClass.java)。フレームワークはすべてのメソッド呼び出しをインターセプトするプロキシオブジェクトを生成します。各呼び出しは事前に定義された期待値と比較されます。呼び出しが期待値と一致する場合は指定された値が返され、一致しない場合は設定に応じてデフォルト値が返されるか例外がスローされます。
Mockは、テスト対象のコードが副作用を持つコンポーネント(サーバーへのデータ送信、データベースへの書き込み、ロギング、アナリティクス、ナビゲーション、システムダイアログの表示など)と相互作用する場合に不可欠です。Mockなしでは、実際のインフラストラクチャを実行せずにこれらの相互作用を検証することはできません。Google Testing Blogによると、Mockはテストサーバーを起動せずにアプリケーションが実際にアナリティクスイベントを送信したことを検証する唯一の方法です。
MockとStubの違いは、テストにおいて最も議論されるトピックの一つです。どちらのタイプも実際の依存関係を置き換えますが、根本的に異なる方法で行います。
| 基準 | Mock | Stub |
|---|---|---|
| 主な質問 | メソッドは呼び出されたか? | どの結果が返されたか? |
| 検証 | 振る舞い(verify) | 状態(assert) |
| データ返却 | オプション | 必須 |
| 例 | verify(analytics).logEvent(“click”) | assertEquals(5, repository.getCount()) |
| 使用時 | 副作用 | データ返却 |
判断のための簡単なテスト:「このコード行を削除したら、テストは失敗するか?」と自問してください。テストが戻り値を検証する場合はStub(assertベースの検証)が必要です。テストがコードが正しい引数でメソッドを呼び出したかを検証する場合はMock(verifyベースの検証)が必要です。この二分法はCommand-Query Separationパターンに従います:状態を変更するメソッド(commands)はMockを必要とし、データを返すメソッド(queries)はStubを必要とします。
MockitoとMockKの選択は、KotlinでAndroidプロジェクトのテストスタックを設定する際の最初の判断の一つです。どちらのライブラリも同じ目的を果たしますが、Kotlin固有の機能に対するアプローチが異なります。
MockitoはJavaプロジェクトの事実上の標準です。バージョン5.xは、組み込みのMockMakerにより、finalクラス、staticメソッド、コンストラクタのモッキングをサポートしています。Kotlinプロジェクトの場合、Mockitoには追加のセットアップが必要です:構文改善のためのmockito-kotlin拡張、finalクラスのためのmockito-inline。Mockitoは追加のアダプタなしではKotlinコルーチンとsuspend関数をサポートしません。
MockKはKotlin向けに特別に作成されました。コルーチン(coEvery、coVerify)、シールドクラス、データクラス、オブジェクトシングルトン、拡張関数をネイティブサポートします。MockKの構文はラムダブロックを使用したDSLを使用し、Kotlinコードで自然に見えます。MockKは追加設定なしでプロパティモッキングもサポートしており、これはLiveData、StateFlow、Delegatesを使用するAndroidプロジェクトにとって重要です。
// Mockito + mockito-kotlin
val repository = mock<UserRepository>()
whenever(repository.getUser(1)).thenReturn(user)
// MockK
val repository = mockk<UserRepository>()
every { repository.getUser(1) } returns user
ベンチマーク(JVM Benchmark、2024)によると、MockKはJava ReflectionsではなくKotlinバイトコードを直接操作するため、KotlinプロジェクトにおいてMockitoよりも15–20%高速にモックオブジェクトを作成します。数千のユニットテストを持つプロジェクトでは、ビルド速度の差が顕著になります:MockKは大規模プロジェクトのテストフル実行で30–60秒を節約します。
3つのシナリオを見てみましょう:Mock依存関係を使用したViewModelのテスト、API呼び出しを検証するUseCaseのテスト、coVerifyを使用したコルーチンのテストです。
class ProfileViewModelTest {
private val analytics = mockk<AnalyticsService>()
private val repo = mockk<UserRepository>()
private val vm = ProfileViewModel(repo, analytics)
fun `profile opened logs analytics event`() {
every { analytics.logEvent("profile_opened") } returns Unit
vm.onViewCreated()
verify { analytics.logEvent("profile_opened") }
}
}
class SendMessageUseCaseTest {
private val api = mockk<MessagingApi>()
private val useCase = SendMessageUseCase(api)
fun `send message with correct payload`() = runTest {
val message = Message(text = "Hello", userId = 42)
coEvery { api.sendMessage(any()) } returns MessageResult.Sent("msg_1")
val result = useCase.execute(message)
coVerify {
api.sendMessage(match {
it.text == "Hello" && it.userId == 42
})
}
assertTrue(result is MessageResult.Sent)
}
}
class OrderUseCaseTest {
private val api = mockk<OrderApi>()
private val useCase = OrderUseCase(api)
private val slot = slot<OrderRequest>()
fun `order request contains correct items`() = runTest {
coEvery { api.placeOrder(capture(slot)) } returns OrderResult.Placed("order_1")
useCase.execute(listOf("item_a", "item_b"))
assertEquals(2, slot.captured.items.size)
assertEquals("item_a", slot.captured.items[0])
}
}
モバイル開発におけるMockの効果的な使用には規律が必要です。これらのルールに違反すると、テストはリファクタリングのたびに壊れる脆い障害物に変わります。
厳格なルール:Mockはアプリケーション境界を越える依存関係に対してのみ作成すべきです:APIクライアント、データベース、ファイルシステム、システムサービス(LocationManager、BluetoothAdapter、Camera)。内部アプリケーションクラス — ドメインエンティティ、Value Objects、単純なユーティリティ — はMockに置き換えるべきではありません。それらの振る舞いは実際のオブジェクトを通じてテストされます。
各テストは、verify(Mockの場合)またはassert(Stubの場合)のいずれか、ちょうど1つの論理チェックを含むべきです。1つのテストで状態と振る舞いの検証を混在させないでください。API呼び出しとその結果の両方をチェックする必要がある場合は、別々の名前で2つの別々のテストを作成してください。このルールは、「1テストに1つのassert」として知られ、Kent Beck(2002)の推奨に由来します。
基本的なモッキングに加えて、モバイル開発における特定のタスクを解決する高度なテクニックがあります:マルチスレッドのテスト、Flow状態の検証、実際のオブジェクトの部分モッキングです。
Spy(または部分mock)を使用すると、実際の実装に呼び出しを委譲しつつ、個別のメソッドをオーバーライドできるオブジェクトを作成できます。MockKでは、spykは実際のクラスインスタンスに基づいて作成されます:val repo = spyk(InMemoryUserRepository())。everyで定義された期待値を持つ呼び出しはMockを通過し、残りは実際のオブジェクトを通過します。Spyは、依存性注入がまだ実装されていないレガシーコードのテストで、1つのメソッドだけをオーバーライドする必要がある場合に特に便利です。
Jetpack Composeを使用する最新のAndroidプロジェクトでは、ViewModelはStateFlowを通じて状態を公開します。MockKはFlow依存関係のモッキングを可能にし、Turbineライブラリは出力検証を簡素化します。古典的なパターン:Flowを返すUseCaseにはMockK、ViewModelの出力検証にはTurbine。このスタックは、Kotlin Coroutinesを使用するプロジェクトに対してAndroid Testingドキュメント(Google、2024)で推奨されています。
class SearchViewModelTest {
private val searchUseCase = mockk<SearchUseCase>()
private val vm = SearchViewModel(searchUseCase)
fun `search emits results`() = runTest {
coEvery { searchUseCase.search("android") } returns
flowOf(SearchResult.Success(listOf(Item("Android TDD"))))
vm.search("android")
vm.state.test {
val state = awaitItem()
assertTrue(state.items.isNotEmpty())
cancelAndIgnoreRemainingEvents()
}
}
}
よくある質問
Mockは概念であり、振る舞いを検証するTest Doubleの一種です。MockitoはJavaおよびAndroidでMockオブジェクトを作成するためのライブラリです。他のライブラリ:MockK(Kotlin)、EasyMock(Java)、Cuckoo(iOS)。
Mockでsuspend関数をテストするには、MockK(coEvery / coVerify)またはmockito-kotlinを使用したMockitoを使用します。MockKはコルーチンをネイティブサポートします:coEveryはsuspend関数の動作を定義し、coVerifyはコルーチン内での呼び出しを検証します。すべてのsuspend呼び出しはrunTest(kotlinx-coroutines-test)内で実行する必要があります。
はい。MockKではreturnsManyを使用します:every { api.getData() } returnsMany listOf(response1, response2)。MockitoではthenReturn(value1).thenReturn(value2)のチェーンを使用します。これは連続した呼び出しで異なる応答を返す動作のテストに便利です。
MockKでは、relaxed = trueを指定した@MockKアノテーションを使用し、@AfterメソッドでclearMocks(mock)を呼び出します。MockitoではMockito.reset(mock)を使用します。ベストプラクティス:@Beforeでテストごとに新しいMockを作成し、テスト間の干渉を排除します。
MockKはシールドクラスで正しく動作します:every { useCase() } returns Result.Success(data)。Mockitoはシールドクラスを直接サポートしておらず、回避策が必要です。これがKotlinプロジェクトでMockitoよりもMockKが推奨される理由の一つです。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。