L'un des avantages des frameworks d'injection de dépendances comme Hilt est qu'ils facilitent le test de votre code.
Tests unitaires
Hilt n'est pas nécessaire pour les tests unitaires. En effet, lorsque vous testez une classe qui utilise l'injection de constructeur, vous n'avez pas besoin de l'utiliser pour instancier cette classe. À la place, vous pouvez appeler directement un constructeur de classe en transmettant des dépendances factices ou fictives, comme si le constructeur n'était pas annoté :
@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(...) } }
Il en va de même pour les classes ViewModel obtenues en appelant hiltViewModel() dans vos composables. Dans les tests unitaires, construisez le ViewModel directement avec des faux.
Pour savoir comment les flux d'état passent d'un ViewModel à des composables, consultez État et Jetpack Compose et Où hisser l'état.
Tests de bout en bout
Pour les tests d'intégration, Hilt injecte des dépendances comme il le ferait dans votre code de production. Effectuer des tests avec Hilt ne nécessite aucune maintenance, dans la mesure où un nouvel ensemble de composants est généré automatiquement pour chaque test.
Ajouter des dépendances de test
Pour utiliser Hilt dans vos tests, incluez la dépendance hilt-android-testing dans votre projet :
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") }
Configuration du test d'interface utilisateur
Vous devez annoter avec @HiltAndroidTest tout test d'interface utilisateur qui utilise Hilt. Cette annotation permet de générer les composants Hilt pour chaque test.
Vous devez également ajouter HiltAndroidRule à la classe de test. Il gère l'état des composants et permet d'effectuer une injection sur votre test :
@HiltAndroidTest class SettingsScreenTest { @get:Rule(order = 0) val hiltRule = HiltAndroidRule(this) @get:Rule(order = 1) val composeRule = createAndroidComposeRule<HiltTestActivity>() // Compose UI tests here. }
Ensuite, votre test doit connaître la classe Application générée automatiquement par Hilt.
Pour permettre à Hilt d'injecter des dépendances, vous devez créer une activité vide nommée HiltTestActivity dans votre ensemble de sources androidTest et l'annoter avec @AndroidEntryPoint. createAndroidComposeRule utilise ensuite cette activité comme hôte pour votre contenu composable.
Tester l'application
Vous devez exécuter les tests d'instrumentation qui utilisent Hilt dans un objet Application compatible avec Hilt. La bibliothèque fournit HiltTestApplication à utiliser dans les tests.
Si vos tests nécessitent une autre application de base, consultez la section Application personnalisée pour les tests.
Vous devez configurer votre application de test pour qu'elle s'exécute dans vos tests d'instrumentation ou Robolectric. Les instructions suivantes ne sont pas spécifiques à Hilt, mais sont des consignes générales pour spécifier une application personnalisée à exécuter dans les tests.
Définir l'application de test dans les tests d'instrumentation
Pour utiliser l'application de test Hilt dans les tests d'instrumentation, vous devez configurer un nouveau lanceur de test. Ce lanceur permet à Hilt de fonctionner pour tous les tests d'instrumentation de votre projet. Procédez comme suit :
- Créez une classe personnalisée qui étend
AndroidJUnitRunnerdans le dossierandroidTest. - Ignorez la fonction
newApplicationet indiquez le nom de l'application de test Hilt générée.
// 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) } }
Configurez ensuite ce lanceur de test dans votre fichier Gradle, comme décrit dans le guide de tests unitaires d'instrumentation. Veillez à utiliser le chemin de classe complet :
android { defaultConfig { // Replace com.example.android.dagger with your class path. testInstrumentationRunner = "com.example.android.dagger.CustomTestRunner" } }
Définir l'application de test dans les tests Robolectric
Si vous utilisez Robolectric pour tester votre couche d'interface utilisateur, vous pouvez spécifier l'application à utiliser dans le fichier robolectric.properties :
application = dagger.hilt.android.testing.HiltTestApplication
Vous pouvez également configurer l'application sur chaque test individuellement à l'aide de l'annotation @Config de Robolectric :
@HiltAndroidTest @Config(application = HiltTestApplication::class) class SettingsScreenTest { @get:Rule var hiltRule = HiltAndroidRule(this) // Robolectric tests here. }
Fonctionnalités de test
Une fois que Hilt est prêt à être utilisé dans vos tests, vous pouvez personnaliser plusieurs processus à l'aide de plusieurs fonctionnalités.
Injecter des types dans les tests
Pour injecter des types dans un test, utilisez @Inject pour l'injection de champs. Pour indiquer à Hilt de renseigner les champs @Inject, appelez hiltRule.inject().
Consultez l'exemple suivant d'un test d'instrumentation :
@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. } }
Remplacer une liaison
Si vous devez injecter une instance fausse ou factice d'une dépendance, vous devez indiquer à Hilt de ne pas utiliser la liaison utilisée dans le code de production et d'en utiliser une autre. Pour remplacer une liaison, vous devez remplacer le module qui contient la liaison par un module de test contenant les liaisons que vous souhaitez utiliser dans le test.
Par exemple, supposons que votre code de production déclare une liaison pour AnalyticsService comme suit :
@Module @InstallIn(SingletonComponent::class) abstract class AnalyticsModule { @Singleton @Binds abstract fun bindAnalyticsService( analyticsServiceImpl: AnalyticsServiceImpl ): AnalyticsService }
Pour remplacer la liaison AnalyticsService dans les tests, créez un module Hilt dans le dossier test ou androidTest avec la fausse dépendance et annotez-le avec @TestInstallIn. Tous les tests de ce dossier sont injectés avec une fausse dépendance.
@Module @TestInstallIn( components = [SingletonComponent::class], replaces = [AnalyticsModule::class] ) abstract class FakeAnalyticsModule { @Singleton @Binds abstract fun bindAnalyticsService( fakeAnalyticsService: FakeAnalyticsService ): AnalyticsService }
Étant donné que les composables consomment généralement ces dépendances indirectement via un ViewModel obtenu avec hiltViewModel(), il suffit de remplacer la liaison dans Hilt. Le composable testé récupère automatiquement le faux.
Remplacer une liaison dans un seul test
Pour remplacer une liaison dans un seul test plutôt que dans tous, désinstallez un module Hilt d'un test à l'aide de l'annotation @UninstallModules et créez un module de test dans le test.
En suivant l'exemple AnalyticsService de la version précédente, commencez par indiquer à Hilt d'ignorer le module de production en utilisant l'annotation @UninstallModules dans la classe de test :
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsScreenTest { ... }
Vous devez ensuite remplacer la liaison. Créez un module dans la classe de test qui définit la liaison de test :
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsScreenTest { @Module @InstallIn(SingletonComponent::class) abstract class TestModule { @Singleton @Binds abstract fun bindAnalyticsService( fakeAnalyticsService: FakeAnalyticsService ): AnalyticsService } // ... }
Cela ne remplace la liaison que pour une seule classe de test. Si vous souhaitez remplacer la liaison pour toutes les classes de test, utilisez l'annotation @TestInstallIn de la section ci-dessus. Vous pouvez également placer la liaison de test dans le module test pour les tests Robolectric ou dans le module androidTest pour les tests d'instrumentation.
Nous vous recommandons d'utiliser @TestInstallIn dans la mesure du possible.
Lier de nouvelles valeurs
Utilisez l'annotation @BindValue pour lier facilement des champs de votre test au graphique de dépendance de Hilt. Annotez un champ avec @BindValue. Il est lié sous le type de champ déclaré avec tous les qualificatifs présents pour ce champ.
Dans l'exemple AnalyticsService, vous pouvez remplacer le service AnalyticsService par un faux à l'aide de @BindValue :
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsScreenTest { @BindValue @JvmField val analyticsService: AnalyticsService = FakeAnalyticsService() ... }
Cela simplifie le remplacement d'une liaison et le référencement d'une liaison dans votre test en vous permettant d'effectuer les deux en même temps.
@BindValue fonctionne avec des qualificatifs et d'autres annotations de test. Par exemple, si vous utilisez des bibliothèques de test telles que Mockito, vous pouvez les utiliser dans un test Robolectric comme suit :
... class SettingsScreenTest { ... @BindValue @ExampleQualifier @Mock lateinit var qualifiedVariable: ExampleCustomType // Robolectric tests here }
Si vous devez ajouter une liaison multiple, vous pouvez utiliser les annotations @BindValueIntoSet et @BindValueIntoMap à la place de @BindValue. @BindValueIntoMap nécessite que vous annotiez le champ avec une annotation de clé de carte.
Cas particuliers
Hilt fournit également des fonctionnalités adaptées aux cas d'utilisation non standards.
Application personnalisée pour les tests
Si vous ne pouvez pas utiliser HiltTestApplication, car votre application de test doit étendre une autre application, annotez une nouvelle classe ou interface avec @CustomTestApplication, en indiquant la valeur de la classe de base que l'application Hilt générée doit étendre.
@CustomTestApplication génère une classe Application prête à être testée avec Hilt, qui étend l'application que vous avez transmise en tant que paramètre.
@CustomTestApplication(BaseApplication::class) interface HiltTestApplication
Dans cet exemple, Hilt génère une Application nommée HiltTestApplication_Application qui étend la classe BaseApplication. En général, le nom de l'application générée est le nom de la classe annotée, suivie du suffixe _Application. Vous devez configurer l'application de test générée par Hilt pour qu'elle s'exécute dans vos tests d'instrumentation ou tests Robolectric comme décrit dans la section Tester l'application.
Plusieurs objets TestRule dans votre test d'instrumentation
Les tests d'interface utilisateur Compose combinent déjà HiltAndroidRule avec une règle de test Compose telle que createAndroidComposeRule. Si vous avez d'autres objets TestRule, assurez-vous que HiltAndroidRule s'exécute en premier. Déclarez l'ordre d'exécution avec l'attribut order sur @Rule :
@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. }
Vous pouvez également encapsuler les règles avec RuleChain, en plaçant HiltAndroidRule comme règle externe.
@HiltAndroidTest class SettingsScreenTest { @get:Rule var rule = RuleChain.outerRule(HiltAndroidRule(this)). around(SettingsScreenTestRule(...)) // UI tests here. }
Utiliser un point d'entrée avant que le composant singleton ne soit disponible
L'annotation @EarlyEntryPoint fournit un mécanisme de sortie lorsqu'un point d'entrée Hilt doit être créé avant que le composant singleton ne soit disponible dans un test Hilt.
Pour en savoir plus sur @EarlyEntryPoint, consultez la documentation Hilt.
Ressources supplémentaires
Pour en savoir plus sur les tests, consultez les ressources supplémentaires suivantes :