Android Mockitoのテストコードを作成する(kotlin)

By JS | Last updated: January 03, 2020

Mockitoは、オブジェクトをmockingに使用されるJavaライブラリです。 Junitに同Unit testを作成するのに使用されます。 AndroidもUnit testを作成するのに正式にMockitoを使用することを推奨しています。

AndroidはJVMで動作するTestがあり、デバイスまたはエミュレーターで動作するInstrumentation testがあります。 Mockitoは二つのテストの両方で利用することができます。

この記事では、次の内容につき説明します。

  • Mockitoを使用してUnit testを作成する方法
  • Android SDKをMockingて、Testする部屋

この記事で使用されるコードは、kotlinで作成されました。

Gradle設定

Android Studioのプロジェクトの build.gradleに次のように依存性を設定します。

testImplementation 'junit:junit:4.12' // junit
androidTestImplementation("org.mockito:mockito-android:2.24.5")
testImplementation 'org.mockito:mockito-inline:2.21.0'

org.mockito:mockito-coreを使用してもしますが、kotlinを使用している場合、org.mockito:mockito-inlineを使用することをお勧めします。 (参考:Mockitoは最終クラスのエラーをモック/スパイできません)

testImplementation 'org.mockito:mockito-inline:2.21.0'
// testImplementation 'org.mockito:mockito-core:2.28.2'

そして、次のように returnDefaultValues = trueに設定します。

android {
    ...
    testOptions {
        unitTests.returnDefaultValues = true
    }
}

アンドロイドUnit testをビルドするときに使用する android.jarは、実際のコードが含まれていません。 上記の設定は、は、テストコードのAPIが実装されていないとき、nullまたは0を返すようにして、テストが行われるようにすることです。 すべてをmockingできないため、必要なものだけmockingし、残りはデフォルト値が返すようにすることが快適ことができます。 (参考:Andorid Developer)

MockingとUnit testを作成

Unit testを作成する前に、次のように単純なクラスを実装します。 このコードは、アプリで使用されるコードであるため、Testフォルダにファイルを作成すると、ありません。

Example.kt

class Example {

    fun getId() : Int {
        // get id from server and return
        return 0
    }

    fun getUrl(id: Int) : String {
        // get url from server and return
        return ""
    }
}

今テストコードを書くことです。

もしいくつかのオブジェクトをテストするのに Exampleオブジェクトとの依存関係があります。 ExampleこのServerからデータを取得またはDBを使用する場合は、テストが困難な場合があります。 テストサーバーを作成したり、テストDBを作成することができます。

Mockitoを使用すると、 ExampleのAPIを呼び出すときにどのような値を返すかどうか決めることができます。 したがって、任意の値が常に返されると考えて依存関係をなくすことができます。 このように、他の依存性を除去し、特定のクラス、Unitのみtestするコードを書くことができます。

次のように ../test/の下にコードを記述します。

import org.junit.Test
import org.junit.Assert.*
import org.junit.runner.RunWith
import org.mockito.Mockito
import org.mockito.junit.MockitoJUnitRunner

@RunWith(MockitoJUnitRunner::class)   // 1
class ExampleUnitTest {

    @Test   // 2
    fun example1() {
        val example = Mockito.mock(Example::class.java)   // 3

        Mockito.`when`(example.getId()).thenReturn(100)   // 4
        Mockito.`when`(example.getUrl(100))
               .thenReturn("https://codechacha.com")    //  5

        assertEquals(100, example.getId())    // 6
        assertEquals("https://codechacha.com",
            example.getUrl(example.getId()))    // 7
    }
}
  1. Mockitoを使用するには、このようにAnnotationを付けます。 ( @RunWith(AndroidJUnit4::class)を付けても動作には問題ですね。)
  2. Junitは @Testがあるメソッドをテストメソッドと認識します。
  3. Exampleクラスをmockingてオブジェクトにしてくれます。
  4. このオブジェクトの getId()が呼び出されたとき、100を返すようにします。
  5. このオブジェクトの getUrl(100)が呼び出されたとき https://codechacha.comを返すようにします。引数が必ず100に渡す必要があります。
  6. getId()が100を返すかどうかを確認します。
  7. getUrl(100)が https://codechacha.comを戻すかどうかを確認します。

もし次のように org.mockito.Mockito.*にimportするコードがより簡潔になります。

import org.mockito.Mockito.*

val example = mock(Example::class.java)
`when`(example.getId()).thenReturn(100)
`when`(example.getUrl(100)).thenReturn("https://codechacha.com")

anyInt(), anyString()

上記のMockingするときの引数に正確な値を入れてくれました。 引数として任意の値を渡すかどうかに関係がない場合は、 anyInt()anyString()などを使用することができます。

以下は、 anyInt()を使用する例です。引数としてどのような値が渡さなろう戻り値は同じです。

@Test
fun example2() {
    val example = mock(Example::class.java)

    `when`(example.getId()).thenReturn(100)
    `when`(example.getUrl(anyInt())).thenReturn("https://codechacha.com")

    assertEquals(100, example.getId())
    assertEquals("https://codechacha.com", example.getUrl(0))
}

そのほか、さまざまな型をサポートするメソッドがあります。

  • anyInt()
  • anyString()
  • anyBoolean()
  • anyDouble()
  • anyFloat()
  • anyList()

Exception発生

いくつかのAPIを呼び出したときにExceptionが発生するようにすることもできます。

以下は、 getUrl(20)を呼び出すときに IllegalStateException例外が渡される例です。

@Test
fun example3() {
    val example = mock(Example::class.java)

    `when`(example.getUrl(anyInt())).thenReturn("https://codechacha.com")
    assertEquals("https://codechacha.com", example.getUrl(10))

    `when`(example.getUrl(20)).thenThrow(IllegalStateException("Exception happened!"))

    try {
        example.getUrl(20)
        fail()
    } catch (e: IllegalStateException) {
        assertEquals(e.message, "Exception happened!")
    }

    doReturn(30).`when`(example).getId()
    assertEquals(30, example.getId())
}

verify()

verifyはどのAPIが呼び出されたか、何度以上呼び出されたチェックするときに使用することができます。 いくつかのメソッドを呼び出すときに、そのメソッドの中で、特定のAPIを呼び出していることを確認したいときに使用することができます。

以下は、verifyを使用する例です。

@Test
fun example4() {
    val example = mock(Example::class.java)
    `when`(example.getId()).thenReturn(100)
    `when`(example.getUrl(100)).thenReturn("https://codechacha.com")

    example.getId()
    example.getId()
    val url = example.getUrl(example.getId())

    verify(example).getUrl(ArgumentMatchers.eq(100))  // 1
    verify(example, times(3)).getId()   // 2
    verify(example, atLeast(2)).getId()   // 3
    verify(example, atLeast(1)).getUrl(100)   // 4
}
  1. getUrl(100)が呼び出されたことを確認します。
  2. getId()が3回呼び出されたことを確認します。 2回呼び出した場合、テストはfailされます。
  3. getId()が少なくとも2回呼び出されていることを確認します。
  4. getUrl(100)が少なくとも1回呼び出されていることを確認します。

そのほか、以下のメソッドを提供します。

  • times()
  • atLeast()
  • atLeastOnce()
  • atMost()

Android SDK를モッキング및単体テスト

これまで直接実装したクラスをMockingてUnit testを作成しました。 今Android SDKのようにライブラリで提供されているオブジェクトをMockingてUnit testを作成してみましょう。

まず、アプリに次のようなコードが実装されていると仮定してみましょう。 PackageManagerを通じて com.codechacha.sampleというアプリがインストールされていることを確認するためのコードです。

MainActivity.kt

class MainActivity : AppCompatActivity() {
    companion object {
        const val TAG = "MainActivity"

        fun isSampleAppInstalled(pm: PackageManager): Boolean {
            val SAMPLE_APP_PKG = "com.codechacha.sample"
            val ris = pm.getInstalledPackages(0)
            Log.d(TAG, "ris : $ris")
            for (ri: PackageInfo in ris) {
                if (ri.packageName == SAMPLE_APP_PKG) {
                    return true
                }
            }
            return false
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        if (isSampleAppInstalled(packageManager)) {
            Log.d(TAG, "Sample app is installed in the device.")
        }
    }
}

上記のコードで isSampleAppInstalled()com.codechacha.sampleというアプリがインストールされている場合はtrueを返します。 テストしたいのはPackageManagerから受信した情報からsampleアプリがあることを見つけるのコードです。

ここで依存性のある部分はPackageManagerです。デバイスにインストールされてアプリの状態に応じて結果が変わるからです。 私たちは、Mockitoを利用して、PackageManagerが常にsampleアプリがインストールされている情報を返すようにすることができます。

以下は、上で述べたように、実際に実装したコードです。

@Test
fun sample_app_is_installed() {
    val pi = PackageInfo()    // 1
    pi.packageName = "com.codechacha.sample"
    val installedApps: List<PackageInfo> = listOf(pi)   // 2

    val pm = Mockito.mock(PackageManager::class.java)  // 3
    Mockito.`when`(pm.getInstalledPackages(0)).thenReturn(installedApps) // 4

    assertTrue(MainActivity.isSampleAppInstalled(pm)) // 5
}
  1. PackageInfoオブジェクトを作成します。
  2. packageNameに"com.codechacha.sample"を設定します。
  3. PackageInfoをリストにします。
  4. getInstalledPackages(0)が呼び出されると、上記で作成しリストを返すようにします。
  5. テストするAPIを呼び出して、trueが返されることを確認します。

もしテストの実行中に以下のようなエラーが発生した場合、上記のgradle設定で紹介した returnDefaultValues = trueを設定してみ再度実行しましょう。

java.lang.RuntimeException: Method toString in android.content.pm.PackageInfo not mocked. See http://g.co/androidstudio/not-mocked for details.

at android.content.pm.PackageInfo.toString(PackageInfo.java)
at java.lang.String.valueOf(String.java:2994)
at java.lang.StringBuilder.append(StringBuilder.java:131)

まとめ

Mockitoを使用してUnit testを作成する方法について説明しました。 MockitoはJVMで動作するUnit testと実際のデバイスまたはエミュレーターで動作するInstrumentation testの両方で使用することができます。 テストするコード以外の依存性をMockitoでなくして、Unitをテストすることができます。

この記事で使用したコードは、GitHubで確認することができます。

参考

Related Posts

codechachaCopyright ©2019 codechacha