MockKはモックオブジェクトを作成するためのKotlin-firstフレームワークであり、Kotlinエコシステム向けに、コルーチン、拡張関数、data class、sealed classといった言語機能を考慮して特別に設計されています。JavaからKotlinに移植されたMockitoとは異なり、MockKは元からKotlin構文用に設計されており、finalクラスを扱うための追加プラグインは必要ありません。MockK.ioによると、このライブラリは単体テストを行うKotlinプロジェクトの40%以上で使用されています。
主要ポイント
MockKはモックオブジェクトを作成するためのライブラリで、Kotlinで書かれ、その構文に最適化されています。Mockitoと同じ問題(依存関係からテスト対象コードを隔離する)を解決しますが、ラムダ、DSL、reified generics、suspend関数といったKotlin固有の構文を使用します。
移植されたソリューションに対するMockKの主な利点はネイティブのKotlinサポートです。Mockitoではfinalクラスのモック化にopt-in(mockito-inline)が必要で、静的メソッドにはmockStaticが必要です。MockKはデフォルトでこれをサポートしています。なぜならKotlinクラスはデフォルトでfinalであり、この制限を回避することはライブラリのアーキテクチャに組み込まれているからです。
バージョン1.13.12(2024年)は安定版リリースで、Kotlin 2.0、K2コンパイラ、マルチプラットフォームプロジェクト(KMP)をサポートしています。MockKはKotlin/NativeやKotlin/JSでも動作し、MockitoもEasyMockも適用できないKMPプロジェクトでは唯一の選択肢となっています。
MockKはKotlinの特性を考慮して設計されており、パフォーマンスを犠牲にすることなく、簡潔で型安全なAPIを提供するために、reified generics、ラムダを用いたDSL、インライン関数といった言語機能を活用しています。
MockKのメカニズムは、ByteBuddyライブラリ(Mockitoと同様)によるバイトコード計装に基づいていますが、それをKotlinフレンドリーなDSLでラップしています。when().thenReturn()チェーンの代わりに、MockKはラムダブロックevery { }とcoEvery { }を使用し、これらは言語の自然な拡張のように見えます。内部では、MockKはラムダ内の呼び出しをインターセプトし、リフレクションでメソッドと引数を分析し、記録されたスタビングルールと照合します。
ブロックevery { mock.method() } returns valueは「メソッドが呼び出されるたびに値を返す」と読みます。この宣言的な構文はKotlinスタイルに近く、when()での引数の順序に関する混乱を排除します。Kotlinのreified genericsのおかげで、モックの型はクラスを明示的に指定しなくても自動的に推論されます。
val repository = mockk<UserRepository>()
// スタビング: findById(1)の各呼び出しはユーザーを返す
every { repository.findById(1) } returns User("Alice")
// 呼び出しと検証
val result = repository.findById(1)
assertEquals("Alice", result.name)
各メソッドを明示的に設定する必要があるMockitoとは異なり、MockKはrelaxed mockをサポートしています。これは任意のメソッドに対して「妥当な」デフォルト値(Listには空リスト、Intには0、Stringには空文字列)を返すモックです。これにより、セットアップコードの量が大幅に削減されます。
// Relaxed mock — すべてのメソッドがデフォルト値を返す
val api = mockk<ApiService>(relaxed = true)
// スタビング不要 — 空のリストを返す
println(api.getUsers()) // []
MockKはモックオブジェクトを作成するいくつかの方法を提供しています。厳格モック用のmockk<T>()(各メソッドを明示的に設定する必要あり)、リラックスドモック用のmockk<T>(relaxed = true)、実際のオブジェクトにスパイを作成するspyk(obj)です。
| 関数 | タイプ | スタビングなしの動作 |
|---|---|---|
| mockk() | 厳格なモック | スタブ化されていないメソッドの呼び出しで例外をスロー |
| mockk(relaxed = true) | リラックスドモック | デフォルト値を返す |
| spyk() | スパイ | スタブが設定されていない場合は実際のメソッドを呼び出す |
| slot() | Argument Captor | 検証のために引数をキャプチャ |
厳格モックとリラックスドモックの選択は状況によって異なります。厳格モックは、テストが動作が定義されていないメソッドを使用しないことを保証し、信頼性を高めます。リラックスドモックは、すべての依存関係が重要ではない迅速なテストのプロトタイピングに便利です。実際には、厳格モックから始めて、スタビングがテスト自体よりも多くの行数を占める場合にのみリラックスドに切り替えることをお勧めします。
everyブロックはMockKにおける中心的なスタビング構文です。ラムダ内で特定の引数を持つメソッド呼び出しを記述し、returnsで値を返すか、throwsで例外をスローするか、answersで応答を計算します。
MockKはテストに必要なすべてのシナリオをサポートしています。値の返却、例外のスロー、引数に基づく応答の計算、順序に基づく複数の応答(呼び出しシーケンス)です。
// 値の返却
every { repo.findById(1) } returns User("Alice")
// 例外のスロー
every { repo.findById(999) } throws NotFoundException()
// 動的応答
every { repo.save(any()) } answers {
val user = firstArg<User>()
user.copy(id = 42)
}
// 応答のシーケンス
every { repo.findAll() } returnsMany listOf(
listOf(User("Alice")),
listOf(User("Bob")),
emptyList()
)
VerifyはMockKにおいて目的はMockito.verify()と同様ですが、Kotlin DSLを使用します: verify { mock.method() }。suspend関数にはcoVerify { mock.suspendMethod() }を使用し、これはコルーチンと正しく動作し、特別なランナーは必要ありません。
MockKはMockitoと同じ修飾子をサポートしています: exactly(1)、atLeast(2)、atMost(5)、wasNot(Called)。構文は最小限で、修飾子はverify { }の最初の引数として渡されます。
// 検証: メソッドが正確に1回呼び出された
verify(exactly = 1) { repo.save(any()) }
// 呼び出し順序の検証
verifySequence {
repo.save(any())
repo.flush()
}
// suspend関数用のcoVerify
coVerify { api.fetchUsers() }
引数の検証にはslot()を使用します。これはArgumentCaptorに類似しています。Slotは呼び出し前に宣言され、everyまたはverifyに渡され、テスト実行後にキャプチャされた値を保持します。
val userSlot = slot<User>()
verify { repo.save(capture(userSlot)) }
assertEquals("Alice", userSlot.captured.name)
MockKはJUnit 5での初期化によるモック作成のための@MockKおよび@RelaxedMockKアノテーションを提供します。MockKExtensionは各テストの前に自動的にモックを作成し、後にクリーンアップします。これはMockitoExtensionと似ていますが、リラックスドモードをサポートしています。
@InjectMockKsアノテーション(または明示的なオブジェクト作成を伴う代替の@MockK)は、テスト対象インスタンスにモックを注入します。これによりボイラープレートが削減され、テストコードがよりクリーンになります。
@ExtendWith(MockKExtension::class)
class UserServiceTest {
@MockK
lateinit var repository: UserRepository
@InjectMockKs
lateinit var service: UserService
@Test
fun `getUser returns user from repository`() {
every { repository.findById(1) } returns User("Alice")
assertEquals("Alice", service.getUser(1)?.name)
}
}
選択はチーム構成とプロジェクトの種類によって異なります。Mockitoはより大きなエコシステム、より多くの例と統合を持っていますが、MockKはよりクリーンなKotlin構文と言語機能のネイティブサポートを提供します。新しいKotlinプロジェクトには、MockKがより慣用的なソリューションとして推奨されます。
| 基準 | MockK | Mockito |
|---|---|---|
| 構文 | Kotlin DSL (every, verify) | Javaスタイル (when, thenReturn) |
| コルーチン | coEvery, coVerify (ネイティブ) | 追加ライブラリが必要 |
| Finalクラス | デフォルトでサポート | mockito-inlineが必要 |
| KMP | サポート | 非サポート |
| Relaxed mock | 内蔵 | 同等機能なし |
| 人気 | Kotlinコミュニティで成長中 | Javaとハイブリッドプロジェクトで主流 |
純粋なKotlinプロジェクト(Javaクラスなし)の場合、MockKが望ましいです。ボイラープレートが少なく、ネイティブのコルーチンサポート、finalクラスに関する驚きがありません。ハイブリッドプロジェクトやJava経験のあるチームの場合、Mockitoは引き続き有効な選択肢です。両方のライブラリは異なるモジュールを通じて同じプロジェクトで使用できます。MockitoからMockKへの移行時は、@Mockアノテーションを@MockKに置き換え、when().thenReturn()ブロックをevery { }形式に書き換えるだけで十分です。
よくある質問
Relaxed mockはスタブ化されていないすべてのメソッドに対してデフォルト値(空リスト、0、null)を返し、例外をスローしません。通常の(厳格な)モックは各メソッドの明示的なスタビングが必要で、そうでなければテストは失敗します。リラックスドモックは迅速なテストに便利で、厳格モックは信頼性の高いテストに適しています。
MockKはmockkStatic()による拡張関数のモック化をサポートしています。これはKotlinの拡張関数がレシーバーを最初のパラメーターとする静的メソッドだからです。各拡張関数について、それが宣言されているクラスを指定する必要があります。
はい、MockKは共通コード向けのKotlin Multiplatform(KMP)をサポートしています。JVM、Native、JSプラットフォームで共通API(mockk()、every、verify)を使用できます。これにより、MockKはMockitoが動作しないKMPプロジェクトの唯一の選択肢となっています。
verifySequence { }を使用します。これは呼び出しが期待される順序で厳密に指定されるブロックです。実際の順序が異なる場合、verifySequenceは最初の不一致呼び出しを示す例外をスローします。
はい、技術的には可能ですが、推奨されません。バイトコード計装レベル(ByteBuddy vs mockito-inline)で競合が発生する可能性があります。プロジェクトが既にMockitoを使用している場合、モジュールの分離を通じてMockKへの段階的な移行が可能です。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。