【Kotlin/Android】MockKの使い方!モックの作成方法とテストコードの実装

【Kotlin/Android】MockKの使い方!モックの作成方法とテストコードの実装

この記事からわかること

  • Android Studio/Kotlinで使えるMockK使い方
  • モックオブジェクト作成方法
  • verifyメソッドの使い方

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

環境

MockKとは?

公式リファレンス: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"
}

最新のバージョンは公式リリースノートを確認してください。

公式リファレンス:MockK-release

モックを作成してテストを実装する方法

今回はテスト自体をJUnitを使用していきます。導入や使い方に関しては以下の記事を参考にしてください。

まず最初に登場人物をまとめておきます。クラウドデータベースから取得したデータを使用してあれこれするRootViewModelクラスをテスト対象とする場合の想定で進めていきます。

CloudRepositoryクラウドデータベースにアクセスを行うクラスがあると仮定します。今回はこのクラスのモックを作成したい状態になります。その場合メソッド内の処理(クラウドとの通信)はどうでもよくて引数と返り値が何かだけに注目します。


class CloudRepository {
    /** サーバーからユーザー情報をフェッチする */
    fun fetchAll(): List<User> {
      // 中の処理はどうでも良い
      return fetchData
    }

    /** ユーザー情報を更新する */
    fun updateUserInfo(name: String): Boolean {
      // 中の処理はどうでも良い
      return result
    }
}

続いてテスト対象であるRootViewModelCloudRepositoryから取得したデータを使用して何かしらの操作するクラスと仮定します。わかりやすくするために処理は非常に簡素ですが実際はもっと複雑な処理をしていると考えてください。このクラスの持つ各メソッドの挙動をテストしたい場合は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. モックオブジェクトのベースを作成
  2. モックオブジェクトの各振る舞いにダミー値をセット
  3. テスト対象クラスにモックを渡す

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() }

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index