Hilt のような依存関係の注入フレームワークを使用する利点の 1 つは、コードのテストが容易になる点です。
単体テスト
単体テストに Hilt は必要ありません。コンストラクタ インジェクションを使用するクラスをテストする場合、Hilt を使用してそのクラスをインスタンス化する必要がないためです。代わりに、コンストラクタにアノテーションが付けられていない場合と同様に、偽の依存関係またはモックの依存関係を渡すことでクラス コンストラクタを直接呼び出すことができます。
@ActivityScoped class AnalyticsAdapter @Inject constructor( private val service: AnalyticsService ) { ... } class AnalyticsAdapterTest { @Test fun `Happy path`() { // You don't need Hilt to create an instance of AnalyticsAdapter. // You can pass a fake or mock AnalyticsService. val adapter = AnalyticsAdapter(fakeAnalyticsService) assertEquals(...) } }
コンポーザブルで hiltViewModel() を呼び出して取得した ViewModel
クラスにも同じことが当てはまります。単体テストでは、偽のデータを使用して ViewModel
を直接構築します。
ViewModel からコンポーザブルへの状態フローの方法については、
状態と Jetpack Compose と 状態をホイストする場所をご覧ください。
エンドツーエンド テスト
統合テストの場合、Hilt は本番環境用のコードと同じように依存関係を注入します。Hilt を使用したテストでは、テストごとに新しいコンポーネントのセットが自動的に生成されるため、メンテナンスは不要です。
テスト用の依存関係の追加
テストで Hilt を使用するには、プロジェクトに hilt-android-testing 依存関係を含めます。
dependencies { // For Robolectric tests. testImplementation("com.google.dagger:hilt-android-testing:2.57.1") kspTest("com.google.dagger:hilt-android-compiler:2.57.1") // For instrumented tests. androidTestImplementation("com.google.dagger:hilt-android-testing:2.57.1") kspAndroidTest("com.google.dagger:hilt-android-compiler:2.57.1") // Compose UI test rule. androidTestImplementation("androidx.compose.ui:ui-test-junit4") debugImplementation("androidx.compose.ui:ui-test-manifest") }
UI テストの設定
Hilt を使用する UI テストには @HiltAndroidTest アノテーションを付ける必要があります。このアノテーションは、各テストに Hilt コンポーネントを生成させるものです。
また、テストクラスに HiltAndroidRule を追加する必要もあります。これは、コンポーネントの状態を管理し、テストのインジェクションに使用されるものです。
@HiltAndroidTest class SettingsScreenTest { @get:Rule(order = 0) val hiltRule = HiltAndroidRule(this) @get:Rule(order = 1) val composeRule = createAndroidComposeRule<HiltTestActivity>() // Compose UI tests here. }
次に、Hilt で自動的に生成される Application クラスについて、テストで把握する必要があります。
Hilt に依存関係を注入させるには、androidTest ソースセットに HiltTestActivity という名前の空のアクティビティを作成し、@AndroidEntryPoint アノテーションを付ける必要があります。createAndroidComposeRule は、このアクティビティをコンポーザブル コンテンツのホストとして使用します。
アプリケーションをテストする
Hilt をサポートする Application オブジェクトで、Hilt を使用するインストゥルメンテーション テストを実行する必要があります。ライブラリがテストで使用する HiltTestApplication を提供します。テストに別のベースアプリが必要な場合は、テスト用のカスタムアプリをご覧ください。
インストゥルメンテーション テストまたは Robolectric テストで、実行するテストアプリを設定する必要があります。以下の手順は、Hilt に固有のものではありませんが、テストで実行するカスタム アプリケーションの指定方法に関する一般的なガイドラインです。
インストゥルメンテーション テストでテストアプリを設定する
インストゥルメンテーション テストで Hilt テストアプリを使用するには、新しいテストランナーを設定する必要があります。これにより、プロジェクト内のすべてのインストゥルメンテーション テストで Hilt が機能するようになります。次の手順を行います。
androidTestフォルダにAndroidJUnitRunnerを拡張するカスタムクラスを作成します。newApplication関数をオーバーライドし、生成される Hilt テストアプリの名前を渡します。
// A custom runner to set up the instrumented application class for tests. class CustomTestRunner : AndroidJUnitRunner() { override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application { return super.newApplication(cl, HiltTestApplication::class.java.name, context) } }
次に、 インストゥルメンテーション単体テスト ガイドの説明に従って、このテストランナーを Gradle ファイルで設定します。次のように完全なクラスパスを使用してください。
android { defaultConfig { // Replace com.example.android.dagger with your class path. testInstrumentationRunner = "com.example.android.dagger.CustomTestRunner" } }
Robolectric テストでテストアプリを設定する
Robolectric を使用して UI レイヤをテストする場合は、使用するアプリを robolectric.properties ファイルで指定します。
application = dagger.hilt.android.testing.HiltTestApplication
Robolectric の @Config アノテーションを使用して、各テストでアプリを個別に設定することもできます。
@HiltAndroidTest @Config(application = HiltTestApplication::class) class SettingsScreenTest { @get:Rule var hiltRule = HiltAndroidRule(this) // Robolectric tests here. }
機能のテスト
テストで Hilt を使用する準備ができたら、いくつかの機能を使用してテストのプロセスをカスタマイズできます。
テストに型を注入する
テストに型を注入するには、フィールド インジェクションのために @Inject を使用します。@Inject フィールドを挿入するように Hilt に指示するには、hiltRule.inject() を呼び出します。
以下にインストゥルメンテーション テストの例を示します。
@HiltAndroidTest class SettingsScreenTest { @get:Rule(order = 0) val hiltRule = HiltAndroidRule(this) @get:Rule(order = 1) val composeRule = createAndroidComposeRule<HiltTestActivity>() @Inject lateinit var analyticsAdapter: AnalyticsAdapter @Before fun init() { hiltRule.inject() } @Test fun settingsScreen_showsTitle() { composeRule.setContent { SettingsScreen() } composeRule.onNodeWithText("Settings").assertIsDisplayed() // analyticsRepository is available here. } }
バインディングを置き換える
依存関係の偽またはモックのインスタンスを注入する必要がある場合は、本番環境のコードで使用されるバインディングではなく、別のバインディングを使用するように、Hilt に指示する必要があります。バインディングを置き換えるには、バインディングを含むモジュールを、テスト用のバインディングを含むテスト モジュールに置き換える必要があります。
たとえば、本番環境のコードで AnalyticsService のバインディングを次のように宣言しているとします。
@Module @InstallIn(SingletonComponent::class) abstract class AnalyticsModule { @Singleton @Binds abstract fun bindAnalyticsService( analyticsServiceImpl: AnalyticsServiceImpl ): AnalyticsService }
テストの AnalyticsService バインディングを置き換えるには、test フォルダまたは androidTest フォルダに新しい Hilt モジュールを疑似依存関係で作成し、@TestInstallIn アノテーションを付けます。そのフォルダ内のすべてのテストが、疑似依存関係で注入されます。
@Module @TestInstallIn( components = [SingletonComponent::class], replaces = [AnalyticsModule::class] ) abstract class FakeAnalyticsModule { @Singleton @Binds abstract fun bindAnalyticsService( fakeAnalyticsService: FakeAnalyticsService ): AnalyticsService }
コンポーザブルは通常、hiltViewModel() で取得した ViewModel
を介してこれらの依存関係を間接的に使用するため、Hilt でバインディングを置き換えるだけで十分です。テスト対象のコンポーザブルは、偽のデータを自動的に取得します。
単一のテストのバインディングを置き換える
すべてのテストではなく、単一のテストのバインディングを置き換えるには、@UninstallModules アノテーションを使用してテストから Hilt モジュールをアンインストールし、テスト内に新しいテスト モジュールを作成します。
以前のバージョンの AnalyticsService の例に沿って、まずテストクラスの @UninstallModules
アノテーションを使用して、本番環境モジュールを無視するように Hilt に指示します。
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsScreenTest { ... }
次に、バインディングを置き換える必要があります。テストクラス内にテスト バインディングを定義する新しいモジュールを作成します。
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsScreenTest { @Module @InstallIn(SingletonComponent::class) abstract class TestModule { @Singleton @Binds abstract fun bindAnalyticsService( fakeAnalyticsService: FakeAnalyticsService ): AnalyticsService } // ... }
これにより、単一のテストクラスのバインディングのみが置き換えられます。すべてのテストクラスのバインディングを置き換える場合は、上記のセクションの @TestInstallIn アノテーションを使用します。あるいは、Robolectric テストであれば、テスト バインディングを test モジュールに配置します。インストゥルメンテーション テストであれば、テスト バインディングを androidTest モジュールに配置します。可能な限り、@TestInstallIn を使用することをおすすめします。
新しい値のバインド
@BindValue アノテーションを使用すると、テストのフィールドを簡単に Hilt 依存関係グラフにバインドできます。フィールドに @BindValue アノテーションを付けると、宣言されたフィールド タイプの下に、そのフィールドに存在する任意の修飾子でバインドされます。
AnalyticsService の例では、@BindValue を使用して AnalyticsService
を偽のものに置き換えることができます。
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsScreenTest { @BindValue @JvmField val analyticsService: AnalyticsService = FakeAnalyticsService() ... }
これにより、テストでのバインディングの置換と参照の両方を同時に行うことができ、処理が簡単になります。
@BindValue は、修飾子やその他のテスト アノテーションとともに使用できます。たとえば、
Mockito などのテスト ライブラリを使用する場合、次のように
Robolectric テストで使用できます。
... class SettingsScreenTest { ... @BindValue @ExampleQualifier @Mock lateinit var qualifiedVariable: ExampleCustomType // Robolectric tests here }
マルチバインディングを追加する必要がある場合は、@BindValueIntoSet アノテーションと @BindValueIntoMap アノテーションを @BindValue の代わりに使用できます。@BindValueIntoMap の場合は、フィールドにマップキー アノテーションを付ける必要もあります。
特別な場合
Hilt では、標準的でないユースケースをサポートする機能も用意されています。
テスト用のカスタムアプリ
テストアプリで別のアプリを拡張する必要があるため HiltTestApplication を使用できない場合は、新しいクラスまたはインターフェースを @CustomTestApplication でアノテーションし、生成された Hilt アプリで拡張する基本クラスの値を渡します。
@CustomTestApplication は、Hilt でテストする Application クラスを生成します。このクラスは、パラメータとして渡したアプリを拡張するクラスです。
@CustomTestApplication(BaseApplication::class) interface HiltTestApplication
この例では、Hilt によって Application という名前の
HiltTestApplication_Application が生成され、BaseApplication クラスを拡張します。一般に、生成されるアプリの名前は、アノテーションの付いたクラスの名前に _Application が追加されたものになります。生成された Hilt テストアプリを、インストゥルメンテーション テストまたは Robolectric テストで実行するように設定する必要があります(テストアプリをご覧ください)。
インストゥルメンテーション テスト内の複数の TestRule オブジェクト
Compose UI テストでは、HiltAndroidRule と Compose テストルール(createAndroidComposeRule など)がすでに組み合わされています。追加の
TestRule オブジェクトがある場合は、HiltAndroidRule が最初に実行されるようにしてください。@Rule の
order 属性を使用して実行順序を宣言します。
@HiltAndroidTest class SettingsScreenTest { @get:Rule(order = 0) var hiltRule = HiltAndroidRule(this) @get:Rule(order = 1) val composeRule = createAndroidComposeRule<HiltTestActivity>() @get:Rule(order = 2) val otherRule = SomeOtherRule() // UI tests here. }
または、RuleChain でルールをラップし、HiltAndroidRule を外側のルールとして配置することもできます。
@HiltAndroidTest class SettingsScreenTest { @get:Rule var rule = RuleChain.outerRule(HiltAndroidRule(this)). around(SettingsScreenTestRule(...)) // UI tests here. }
シングルトン コンポーネントが使用可能になる前にエントリ ポイントを使用する
Hilt テストのシングルトン コンポーネントが使用可能になる前に Hilt エントリ ポイントを作成する必要がある場合は、@EarlyEntryPoint アノテーションでエスケープ ハッチを使用できます。
@EarlyEntryPoint について詳しくは、
Hilt のドキュメントをご覧ください。
参考情報
テストの詳細については、次のリソースをご覧ください。