【Android】Compose × Hilt × ViewModelのUIテストで発生したエラー：一体何が起こった？

# はじめに

皆さん、ごきげんよう！れぶです！

今回の記事では、`Compose` × `Hilt` × `ViewModel`を使用したプロジェクトでUIテストする場合に沼った箇所があったので、その内容と解決方法を記していきます。

`UIテスト`でHiltが絡むと意外と厄介だと発見したので、**今後上記のようなプロジェクトでUIテストする方には特に参考になる**のではないかと思います。

約7分以内で読めます。それでは、参りましょう！！

# 開発環境

* MacBook Air
* Android Studio Flamingo | 2022.2.1
* Kotlin 1.8.0
* Compose 1.4.2
* compileSdkVersion 33
* minSdkVersion 21

# ハマった箇所

下記のようにHiltのUIテストに必要な記述を追加して、`testMyComposable()`を開始した場合に事件は起こります。

```ComposeTest.kt
@HiltAndroidTest
class ComposeTest {

    @get:Rule(order = 0)
    var hiltRule = HiltAndroidRule(this)

    @get:Rule(order = 1)
    val composeTestRule = createComposeRule()

    private lateinit var viewModel: MyViewModel

    @Before
    fun setup() {
        hiltRule.inject()
    }

    @Test
    fun testMyComposable() {
        composeTestRule.setContent {
            viewModel = hiltViewModel()
            MyComposable(viewModel)
        }
        // ...
    }
}
```

以下のエラーが出てきます。通常のCompoeのUIテストのように書いてもうまくいきません。

> Hilt test, com.sampleapp.ComposeTest, cannot use a @HiltAndroidApp application .To fix, configure the test to use HiltTestApplication or a custom Hilt test application generated with @CustomTestApplication.

「テストクラスが、`HiltAndroidApp`アノテーションが付いた通常のアプリケーションクラスを使用することはできないぞ」と、怒られています。Hiltが絡むテストでは、**Hiltが提供する独自のテストアプリケーションクラスを使用する必要がある**ためです。

根本の原因はCompose側ではなく、Hilt側の問題でした。

# 解決方法

このエラーを解消するためには、テストクラスが`HiltTestApplication`を使用するように設定するか、CustomTestApplicationアノテーションを付けたカスタムテストアプリケーションクラスを作成して使用するかの二択になります。今回は前者で対応します。

以下はエラー解決の3ステップです。

**1. HiltTestApplicationが使用するようにCustomTestRunnerを設定**

```CustomTestRunner.kt
package com.sampleapp

class CustomTestRunner : AndroidJUnitRunner() {

    override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application {
        return super.newApplication(cl, HiltTestApplication::class.java.name, context)
    }
}
```

```build.gradle(app)
defaultConfig {
        //testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        testInstrumentationRunner "com.sampleapp.CustomTestRunner"
}
```

通常テストランナーは`JUnit`によってテストケースを実行する方法を決定します。

しかし今回は`HiltTestApplication`を使用したいので、HiltTestApplicationインスタンスを生成するテストランナーを独自に作成し、build.gradleに指定します。

**2. AndroidEntryPointアノテーションが付いたテスト用のActivityをdebugフォルダに新規作成**

```HiltTestActivity.kt
@AndroidEntryPoint
class HiltTestActivity : ComponentActivity()
```
```AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>  
<manifest xmlns:android="http://schemas.android.com/apk/res/android"  
    package="com.sampleapp">  
  
    <application>  
        <activity  
            android:name=".HiltTestActivity"  
            android:exported="false" />  
    </application>  
  
</manifest>  
```

いわゆる、ダミーActivityです。Manifestへの登録も忘れずに。

**3. createAndroidComposeRule()に、上記で作ったActivityを指定**

```ComposeTest.kt
@HiltAndroidTest
class ComposeTest {

    @get:Rule(order = 0)
    var hiltRule = HiltAndroidRule(this)

    @get:Rule(order = 1)
    val composeTestRule = createAndroidComposeRule<HiltTestActivity>()

    private lateinit var viewModel: MyViewModel

    @Before
    fun setup() {
        hiltRule.inject()
    }

    @Test
    fun testMyComposable() {
        composeTestRule.setContent {
            viewModel = hiltViewModel()
            MyComposable(viewModel)
        }
        // ...
    }
}
```

今回のようにActivityに直接アクセスし、そこへComposable関数を起動させる場合には、`createAndroidComposeRule`を使用します。

# おわりに
今回は`Compose` × `Hilt` × `ViewModel`を使用したプロジェクトでUIテストする場合に発生したエラーに対する対応を記述しました。

自分でカスタムランナーやダミーActivityを作って、設定し直す作業は「とてもめんどいな」と感じた方も少なくないと思います。自分もその1人です。しかし、UIテストにおける引き出しが更に増えたとポジティブに捉えて対応していきましょう。何せ実装面では、HiltによってDIが自動化できるメリットを享受できていますから。

以上です。同じエラーで困ってる方にこの記事が少しでも役立つと嬉しいです。ありがとうございました！

# 参考サイト
* [Jetpack Compose + ViewModel + Navigation + Hilt で UI テスト](http://y-anz-m.blogspot.com/2021/08/jetpack-compose-viewmodel-navigation_24.html)
* [Hilt テストガイド](https://developer.android.com/training/dependency-injection/hilt-testing?hl=ja)
* [@HiltViewModel を使用した Jetpack Compose インストルメント テスト](https://stackoverflow.com/questions/68826881/jetpack-compose-instrument-test-with-hiltviewmodel)
* [Compose レイアウトのテスト](https://developer.android.com/jetpack/compose/testing?hl=ja)