Hilt のような依存関係の注入フレームワークを使用する利点の 1 つは、コードのテストが容易になる点です。
単体テスト
単体テストに Hilt は必要ありません。コンストラクタ インジェクションを使用するクラスをテストする場合、Hilt を使用してそのクラスをインスタンス化する必要がないためです。代わりに、コンストラクタにアノテーションが付けられていない場合と同様に、偽の依存関係またはモックの依存関係を渡すことでクラス コンストラクタを直接呼び出すことができます。
Kotlin
@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(...) } }
Java
@ActivityScope public class AnalyticsAdapter { private final AnalyticsService analyticsService; @Inject AnalyticsAdapter(AnalyticsService analyticsService) { this.analyticsService = analyticsService; } } public final class AnalyticsAdapterTest { @Test public void happyPath() { // You don't need Hilt to create an instance of AnalyticsAdapter. // You can pass a fake or mock AnalyticsService. AnalyticsAdapter adapter = new AnalyticsAdapter(fakeAnalyticsService); assertEquals(...); } }
エンドツーエンド テスト
統合テストの場合、Hilt は本番環境用のコードと同じように依存関係を注入します。Hilt を使用したテストでは、テストごとに新しいコンポーネントのセットが自動的に生成されるため、メンテナンスは不要です。
テスト用の依存関係の追加
テストで Hilt を使用するには、プロジェクトに hilt-android-testing
依存関係を含めます。
Groovy
dependencies { // For Robolectric tests. testImplementation 'com.google.dagger:hilt-android-testing:2.51.1' // ...with Kotlin. kaptTest 'com.google.dagger:hilt-android-compiler:2.51.1' // ...with Java. testAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.51.1' // For instrumented tests. androidTestImplementation 'com.google.dagger:hilt-android-testing:2.51.1' // ...with Kotlin. kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.51.1' // ...with Java. androidTestAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.51.1' }
Kotlin
dependencies { // For Robolectric tests. testImplementation("com.google.dagger:hilt-android-testing:2.51.1") // ...with Kotlin. kaptTest("com.google.dagger:hilt-android-compiler:2.51.1") // ...with Java. testAnnotationProcessor("com.google.dagger:hilt-android-compiler:2.51.1") // For instrumented tests. androidTestImplementation("com.google.dagger:hilt-android-testing:2.51.1") // ...with Kotlin. kaptAndroidTest("com.google.dagger:hilt-android-compiler:2.51.1") // ...with Java. androidTestAnnotationProcessor("com.google.dagger:hilt-android-compiler:2.51.1") }
UI テストの設定
Hilt を使用する UI テストには @HiltAndroidTest
アノテーションを付ける必要があります。このアノテーションは、各テストに Hilt コンポーネントを生成させるものです。
また、テストクラスに HiltAndroidRule
を追加する必要もあります。これは、コンポーネントの状態を管理し、テストのインジェクションに使用されるものです。
Kotlin
@HiltAndroidTest class SettingsActivityTest { @get:Rule var hiltRule = HiltAndroidRule(this) // UI tests here. }
Java
@HiltAndroidTest public final class SettingsActivityTest { @Rule public HiltAndroidRule hiltRule = new HiltAndroidRule(this); // UI tests here. }
次に、Hilt で自動的に生成される Application
クラスについて、テストで把握する必要があります。
アプリのテスト
Hilt をサポートする Application
オブジェクトで、Hilt を使用するインストゥルメンテーション テストを実行する必要があります。ライブラリがテストで使用する HiltTestApplication
を提供します。テストに別のベースアプリが必要な場合は、テスト用のカスタムアプリをご覧ください。
インストゥルメンテーション テストまたは Robolectric テストで、実行するテストアプリを設定する必要があります。以下の手順は、Hilt に固有のものではありませんが、テストで実行するカスタム アプリケーションの指定方法に関する一般的なガイドラインです。
インストゥルメンテーション テストでテストアプリを設定する
インストゥルメンテーション テストで Hilt テストアプリを使用するには、新しいテストランナーを設定する必要があります。これにより、プロジェクト内のすべてのインストゥルメンテーション テストで Hilt が機能するようになります。次の手順を行います。
androidTest
フォルダにAndroidJUnitRunner
を拡張するカスタムクラスを作成します。newApplication
関数をオーバーライドし、生成される Hilt テストアプリの名前を渡します。
Kotlin
// 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) } }
Java
// A custom runner to set up the instrumented application class for tests. public final class CustomTestRunner extends AndroidJUnitRunner { @Override public Application newApplication(ClassLoader cl, String className, Context context) throws ClassNotFoundException, IllegalAccessException, InstantiationException { return super.newApplication(cl, HiltTestApplication.class.getName(), context); } }
次に、インストゥルメンテーション単体テストガイドの説明に従って、このテストランナーを Gradle ファイルで設定します。次のように完全なクラスパスを使用してください。
Groovy
android { defaultConfig { // Replace com.example.android.dagger with your class path. testInstrumentationRunner "com.example.android.dagger.CustomTestRunner" } }
Kotlin
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
アノテーションを使用して、各テストでアプリを個別に設定することもできます。
Kotlin
@HiltAndroidTest @Config(application = HiltTestApplication::class) class SettingsActivityTest { @get:Rule var hiltRule = HiltAndroidRule(this) // Robolectric tests here. }
Java
@HiltAndroidTest @Config(application = HiltTestApplication.class) class SettingsActivityTest { @Rule public HiltAndroidRule hiltRule = new HiltAndroidRule(this); // Robolectric tests here. }
4.2 より前のバージョンの Android Gradle プラグインを使用している場合は、モジュールの build.gradle
ファイルで次の構成を適用して、ローカル単体テストの @AndroidEntryPoint
クラスの変換を有効にします。
Groovy
hilt { enableTransformForLocalTests = true }
Kotlin
hilt { enableTransformForLocalTests = true }
enableTransformForLocalTests
について詳しくは、Hilt のドキュメントをご覧ください。
機能のテスト
テストで Hilt を使用する準備ができたら、いくつかの機能を使用してテストのプロセスをカスタマイズできます。
テストに型を注入する
テストに型を注入するには、フィールド インジェクションのために @Inject
を使用します。@Inject
フィールドを挿入するように Hilt に指示するには、hiltRule.inject()
を呼び出します。
以下にインストゥルメンテーション テストの例を示します。
Kotlin
@HiltAndroidTest class SettingsActivityTest { @get:Rule var hiltRule = HiltAndroidRule(this) @Inject lateinit var analyticsAdapter: AnalyticsAdapter @Before fun init() { hiltRule.inject() } @Test fun `happy path`() { // Can already use analyticsAdapter here. } }
Java
@HiltAndroidTest public final class SettingsActivityTest { @Rule public HiltAndroidRule hiltRule = new HiltAndroidRule(this); @Inject AnalyticsAdapter analyticsAdapter; @Before public void init() { hiltRule.inject(); } @Test public void happyPath() { // Can already use analyticsAdapter here. } }
バインディングを置き換える
依存関係の偽またはモックのインスタンスを注入する必要がある場合は、本番環境のコードで使用されるバインディングではなく、別のバインディングを使用するように、Hilt に指示する必要があります。バインディングを置き換えるには、バインディングを含むモジュールを、テスト用のバインディングを含むテスト モジュールに置き換える必要があります。
たとえば、本番環境のコードで AnalyticsService
のバインディングを次のように宣言しているとします。
Kotlin
@Module @InstallIn(SingletonComponent::class) abstract class AnalyticsModule { @Singleton @Binds abstract fun bindAnalyticsService( analyticsServiceImpl: AnalyticsServiceImpl ): AnalyticsService }
Java
@Module @InstallIn(SingletonComponent.class) public abstract class AnalyticsModule { @Singleton @Binds public abstract AnalyticsService bindAnalyticsService( AnalyticsServiceImpl analyticsServiceImpl ); }
テストの AnalyticsService
バインディングを置き換えるには、test
フォルダまたは androidTest
フォルダに新しい Hilt モジュールを疑似依存関係で作成し、@TestInstallIn
アノテーションを付けます。そのフォルダ内のすべてのテストが、疑似依存関係で注入されます。
Kotlin
@Module @TestInstallIn( components = [SingletonComponent::class], replaces = [AnalyticsModule::class] ) abstract class FakeAnalyticsModule { @Singleton @Binds abstract fun bindAnalyticsService( fakeAnalyticsService: FakeAnalyticsService ): AnalyticsService }
Java
@Module @TestInstallIn( components = SingletonComponent.class, replaces = AnalyticsModule.class ) public abstract class FakeAnalyticsModule { @Singleton @Binds public abstract AnalyticsService bindAnalyticsService( FakeAnalyticsService fakeAnalyticsService ); }
単一のテストのバインディングを置き換える
すべてのテストではなく、単一のテストのバインディングを置き換えるには、@UninstallModules
アノテーションを使用してテストから Hilt モジュールをアンインストールし、テスト内に新しいテスト モジュールを作成します。
以前のバージョンの AnalyticsService
の例に沿って、まずテストクラスの @UninstallModules
アノテーションを使用して、本番環境モジュールを無視するように Hilt に指示します。
Kotlin
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsActivityTest { ... }
Java
@UninstallModules(AnalyticsModule.class) @HiltAndroidTest public final class SettingsActivityTest { ... }
次に、バインディングを置き換える必要があります。テストクラス内にテスト バインディングを定義する新しいモジュールを作成します。
Kotlin
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsActivityTest { @Module @InstallIn(SingletonComponent::class) abstract class TestModule { @Singleton @Binds abstract fun bindAnalyticsService( fakeAnalyticsService: FakeAnalyticsService ): AnalyticsService } ... }
Java
@UninstallModules(AnalyticsModule.class) @HiltAndroidTest public final class SettingsActivityTest { @Module @InstallIn(SingletonComponent.class) public abstract class TestModule { @Singleton @Binds public abstract AnalyticsService bindAnalyticsService( FakeAnalyticsService fakeAnalyticsService ); } ... }
これにより、単一のテストクラスのバインディングのみが置き換えられます。すべてのテストクラスのバインディングを置き換える場合は、上記のセクションの @TestInstallIn
アノテーションを使用します。あるいは、Robolectric テストであれば、テスト バインディングを test
モジュールに配置します。インストゥルメンテーション テストであれば、テスト バインディングを androidTest
モジュールに配置します。可能な限り、@TestInstallIn
を使用することをおすすめします。
新しい値のバインド
@BindValue
アノテーションを使用すると、テストのフィールドを簡単に Hilt 依存関係グラフにバインドできます。フィールドに @BindValue
アノテーションを付けると、宣言されたフィールド タイプの下に、そのフィールドに存在する任意の修飾子でバインドされます。
AnalyticsService
の例では、@BindValue
を使用して AnalyticsService
を偽のものに置き換えることができます。
Kotlin
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsActivityTest { @BindValue @JvmField val analyticsService: AnalyticsService = FakeAnalyticsService() ... }
Java
@UninstallModules(AnalyticsModule.class) @HiltAndroidTest class SettingsActivityTest { @BindValue AnalyticsService analyticsService = FakeAnalyticsService(); ... }
これにより、テストでのバインディングの置換と参照の両方を同時に行うことができ、処理が簡単になります。
@BindValue
は、修飾子やその他のテスト アノテーションとともに使用できます。たとえば、Mockito などのテスト ライブラリを使用する場合、次のように Robolectric テストで使用できます。
Kotlin
... class SettingsActivityTest { ... @BindValue @ExampleQualifier @Mock lateinit var qualifiedVariable: ExampleCustomType // Robolectric tests here }
Java
... class SettingsActivityTest { ... @BindValue @ExampleQualifier @Mock ExampleCustomType qualifiedVariable; // Robolectric tests here }
マルチバインディングを追加する必要がある場合は、@BindValue
の代わりに @BindValueIntoSet
アノテーションと @BindValueIntoMap
アノテーションを使用できます。@BindValueIntoMap
の場合は、フィールドにマップキー アノテーションを付ける必要もあります。
特別な場合
Hilt では、標準的でないユースケースをサポートする機能も用意されています。
テスト用のカスタムアプリ
テストアプリで別のアプリを拡張する必要があるため HiltTestApplication
を使用できない場合は、新しいクラスまたはインターフェースを @CustomTestApplication
でアノテーションし、生成された Hilt アプリで拡張する基本クラスの値を渡します。
@CustomTestApplication
は、Hilt でテストする Application
クラスを生成します。このクラスは、パラメータとして渡したアプリを拡張するクラスです。
Kotlin
@CustomTestApplication(BaseApplication::class) interface HiltTestApplication
Java
@CustomTestApplication(BaseApplication.class) interface HiltTestApplication { }
この例では、BaseApplication
クラスを拡張する HiltTestApplication_Application
という Application
が Hilt によって生成されます。一般に、生成されるアプリの名前は、アノテーションの付いたクラスの名前に _Application
が追加されたものになります。生成された Hilt テストアプリを、インストゥルメンテーション テストまたは Robolectric テストで実行するように設定する必要があります(テストアプリをご覧ください)。
インストゥルメンテーション テスト内の複数の TestRule オブジェクト
テストに他の TestRule
オブジェクトがある場合には、すべてのルールを共存させる方法が複数あります。
次のようにして、ルールを 1 つにまとめることができます。
Kotlin
@HiltAndroidTest class SettingsActivityTest { @get:Rule var rule = RuleChain.outerRule(HiltAndroidRule(this)). around(SettingsActivityTestRule(...)) // UI tests here. }
Java
@HiltAndroidTest public final class SettingsActivityTest { @Rule public RuleChain rule = RuleChain.outerRule(new HiltAndroidRule(this)) .around(new SettingsActivityTestRule(...)); // UI tests here. }
別の方法として、HiltAndroidRule
が最初に実行される場合に限り、両方のルールを同じレベルで使用できます。@Rule
アノテーションの order
属性を使用して実行順序を指定します。これは JUnit バージョン 4.13 以降でのみ使用できます。
Kotlin
@HiltAndroidTest class SettingsActivityTest { @get:Rule(order = 0) var hiltRule = HiltAndroidRule(this) @get:Rule(order = 1) var settingsActivityTestRule = SettingsActivityTestRule(...) // UI tests here. }
Java
@HiltAndroidTest public final class SettingsActivityTest { @Rule(order = 0) public HiltAndroidRule hiltRule = new HiltAndroidRule(this); @Rule(order = 1) public SettingsActivityTestRule settingsActivityTestRule = new SettingsActivityTestRule(...); // UI tests here. }
launchFragmentInContainer
androidx.fragment:fragment-testing
ライブラリの launchFragmentInContainer
は、@AndroidEntryPoint
アノテーションが付いていないアクティビティに依存するため、Hilt では使用できません。
代わりに、GitHub リポジトリ(architecture-samples
)のコード(launchFragmentInHiltContainer
)を使用してください。
シングルトン コンポーネントが使用可能になる前にエントリ ポイントを使用する
Hilt テストのシングルトン コンポーネントが使用可能になる前に Hilt エントリ ポイントを作成する必要がある場合は、@EarlyEntryPoint
アノテーションでエスケープ ハッチを使用できます。
@EarlyEntryPoint
について詳しくは、Hilt のドキュメントをご覧ください。