Uno dei vantaggi dell'utilizzo di framework di dependency injection come Hilt è che semplifica i test del codice.
Test delle unità
Hilt non è necessario per i test delle unità, poiché quando testi una classe che utilizza constructor injection, non è necessario utilizzare Hilt per creare un'istanza della classe. Puoi invece chiamare direttamente un costruttore di classe passando dipendenze fittizie o simulate, proprio come faresti se il costruttore non fosse annotato:
@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(...) } }
Lo stesso vale per le classi ViewModel ottenute chiamando hiltViewModel() nei tuoi componibili. Nei test delle unità, crea il ViewModel direttamente con i falsi.
Per informazioni su come lo stato passa da un ViewModel ai componibili, consulta Stato e Jetpack Compose e Dove eseguire l'hoisting dello stato.
Test end-to-end
Per i test di integrazione, Hilt inserisce le dipendenze come farebbe nel codice di produzione. I test con Hilt non richiedono manutenzione perché Hilt genera automaticamente un nuovo insieme di componenti per ogni test.
Aggiunta di dipendenze di test
Per utilizzare Hilt nei test, includi la dipendenza hilt-android-testing nel tuo progetto:
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") }
Configurazione del test dell'interfaccia utente
Devi annotare qualsiasi test della UI che utilizza Hilt con @HiltAndroidTest. Questa annotazione è responsabile della generazione dei componenti Hilt per ogni test.
Inoltre, devi aggiungere HiltAndroidRule alla classe di test. Gestisce lo stato dei componenti e viene utilizzato per eseguire l'inserimento nel test:
@HiltAndroidTest class SettingsScreenTest { @get:Rule(order = 0) val hiltRule = HiltAndroidRule(this) @get:Rule(order = 1) val composeRule = createAndroidComposeRule<HiltTestActivity>() // Compose UI tests here. }
Successivamente, il test deve conoscere la classe Application che Hilt
genera automaticamente per te.
Per consentire a Hilt di inserire le dipendenze, devi creare un'attività vuota denominata HiltTestActivity nel set di risorse androidTest e annotarla con @AndroidEntryPoint. createAndroidComposeRule utilizza quindi questa attività come host per i tuoi contenuti componibili.
Testa l'applicazione
Devi eseguire i test di instrumentazione che utilizzano Hilt in un oggetto Application che supporta Hilt. La libreria fornisce HiltTestApplication da utilizzare nei test.
Se i test richiedono un'applicazione di base diversa, consulta Applicazione personalizzata per i test.
Devi impostare l'applicazione di test in modo che venga eseguita nei test di instrumentazione o nei test Robolectric. Le seguenti istruzioni non sono specifiche per Hilt, ma sono linee guida generali su come specificare un'applicazione personalizzata da eseguire nei test.
Imposta l'applicazione di test nei test di instrumentazione
Per utilizzare l'applicazione di test Hilt nei test di instrumentazione, devi configurare un nuovo runner di test. In questo modo, Hilt funzionerà per tutti i test di instrumentazione del progetto. Segui questi passaggi:
- Crea una classe personalizzata che estende
AndroidJUnitRunnernella cartellaandroidTest. - Esegui l'override della funzione
newApplicatione passa il nome dell'applicazione di test Hilt generata.
// 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) } }
Successivamente, configura questo test runner nel file Gradle come descritto nella guida ai test delle unità strumentate. Assicurati di utilizzare il percorso di classe completo:
android { defaultConfig { // Replace com.example.android.dagger with your class path. testInstrumentationRunner = "com.example.android.dagger.CustomTestRunner" } }
Imposta l'applicazione di test nei test Robolectric
Se utilizzi Robolectric per testare il livello UI, puoi specificare quale applicazione utilizzare nel file robolectric.properties:
application = dagger.hilt.android.testing.HiltTestApplication
In alternativa, puoi configurare l'applicazione su ogni test singolarmente utilizzando l'annotazione @Config di Robolectric:
@HiltAndroidTest @Config(application = HiltTestApplication::class) class SettingsScreenTest { @get:Rule var hiltRule = HiltAndroidRule(this) // Robolectric tests here. }
Funzionalità di test
Una volta che Hilt è pronto per essere utilizzato nei test, puoi utilizzare diverse funzionalità per personalizzare il processo di test.
Inserisci i tipi nei test
Per inserire i tipi in un test, utilizza @Inject per il field injection. Per indicare a Hilt di compilare i campi @Inject, chiama hiltRule.inject().
Di seguito è riportato un esempio di test di instrumentazione:
@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. } }
Sostituisci un'associazione
Se devi inserire un'istanza fittizia o simulata di una dipendenza, devi indicare a Hilt di non utilizzare l'associazione utilizzata nel codice di produzione, ma di utilizzarne un'altra. Per sostituire un'associazione, devi sostituire il modulo che la contiene con un modulo di test che contiene le associazioni che vuoi utilizzare nel test.
Ad esempio, supponiamo che il codice di produzione dichiari un'associazione per AnalyticsService come segue:
@Module @InstallIn(SingletonComponent::class) abstract class AnalyticsModule { @Singleton @Binds abstract fun bindAnalyticsService( analyticsServiceImpl: AnalyticsServiceImpl ): AnalyticsService }
Per sostituire l'associazione AnalyticsService nei test, crea un nuovo modulo Hilt nella cartella test o androidTest con la dipendenza fittizia e annotala con @TestInstallIn. Invece, tutti i test in quella cartella vengono inseriti con la dipendenza fittizia.
@Module @TestInstallIn( components = [SingletonComponent::class], replaces = [AnalyticsModule::class] ) abstract class FakeAnalyticsModule { @Singleton @Binds abstract fun bindAnalyticsService( fakeAnalyticsService: FakeAnalyticsService ): AnalyticsService }
Poiché i componibili in genere utilizzano queste dipendenze indirettamente tramite un ViewModel ottenuto con hiltViewModel(), è sufficiente sostituire l'associazione in Hilt. Il composable in fase di test rileva automaticamente il finto.
Sostituisci un'associazione in un singolo test
Per sostituire un'associazione in un singolo test anziché in tutti i test, disinstalla un modulo Hilt da un test utilizzando l'annotazione @UninstallModules e crea un nuovo modulo di test all'interno del test.
Seguendo l'esempio di AnalyticsService della versione precedente, inizia indicando a Hilt di ignorare il modulo di produzione utilizzando l'annotazione @UninstallModules nella classe di test:
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsScreenTest { ... }
Successivamente, devi sostituire l'associazione. Crea un nuovo modulo all'interno della classe di test che definisce l'associazione di test:
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsScreenTest { @Module @InstallIn(SingletonComponent::class) abstract class TestModule { @Singleton @Binds abstract fun bindAnalyticsService( fakeAnalyticsService: FakeAnalyticsService ): AnalyticsService } // ... }
In questo modo, viene sostituita solo l'associazione per una singola classe di test. Se vuoi sostituire
l'associazione per tutte le classi di test, utilizza l'annotazione @TestInstallIn della
sezione precedente. In alternativa, puoi inserire l'associazione di test nel modulo test per i test Robolectric o nel modulo androidTest per i test di instrumentazione.
Il consiglio è di utilizzare @TestInstallIn quando possibile.
Associazione di nuovi valori
Utilizza l'annotazione @BindValue per associare facilmente i campi del test al grafico delle dipendenze di Hilt. Annota un campo con @BindValue e verrà associato al tipo di campo dichiarato con eventuali qualificatori presenti per quel campo.
Nell'esempio AnalyticsService, puoi sostituire AnalyticsService con un
falso utilizzando @BindValue:
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsScreenTest { @BindValue @JvmField val analyticsService: AnalyticsService = FakeAnalyticsService() ... }
In questo modo, è più semplice sostituire un binding e farvi riferimento nel test, perché puoi eseguire entrambe le operazioni contemporaneamente.
@BindValue funziona con i qualificatori e altre annotazioni di test. Ad esempio, se utilizzi librerie di test come Mockito, puoi utilizzarle in un test Robolectric come segue:
... class SettingsScreenTest { ... @BindValue @ExampleQualifier @Mock lateinit var qualifiedVariable: ExampleCustomType // Robolectric tests here }
Se devi aggiungere un multibinding,
puoi utilizzare le annotazioni @BindValueIntoSet e @BindValueIntoMap al posto
di @BindValue. @BindValueIntoMap richiede anche di annotare il campo con un'annotazione della chiave della mappa.
Casi speciali
Hilt fornisce anche funzionalità per supportare casi d'uso non standard.
Applicazione personalizzata per i test
Se non puoi utilizzare HiltTestApplication perché la tua applicazione di test deve estendere un'altra applicazione, annota una nuova classe o interfaccia con @CustomTestApplication, passando il valore della classe base che vuoi che l'applicazione Hilt generata estenda.
@CustomTestApplication genererà una classe Application pronta per il test con Hilt che estende l'applicazione che hai passato come parametro.
@CustomTestApplication(BaseApplication::class) interface HiltTestApplication
Nell'esempio, Hilt genera un Application denominato HiltTestApplication_Application che estende la classe BaseApplication. In generale, il nome dell'applicazione generata è il nome della classe annotata con l'aggiunta di _Application. Devi impostare l'applicazione di test Hilt generata in modo che venga eseguita nei tuoi test di instrumentazione o test Robolectric, come descritto in Applicazione di test.
Più oggetti TestRule nel test di instrumentazione
I test della UI di Compose combinano già HiltAndroidRule con una regola di test di Compose, come createAndroidComposeRule. Se hai altri oggetti TestRule, assicurati che HiltAndroidRule venga eseguito per primo. Dichiara l'ordine di esecuzione con l'attributo order su @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. }
In alternativa, puoi aggregare le regole con RuleChain, posizionando HiltAndroidRule come regola esterna.
@HiltAndroidTest class SettingsScreenTest { @get:Rule var rule = RuleChain.outerRule(HiltAndroidRule(this)). around(SettingsScreenTestRule(...)) // UI tests here. }
Utilizza un entry point prima che il componente singleton sia disponibile
L'annotazione @EarlyEntryPoint fornisce una via di fuga quando è necessario creare un entry point Hilt prima che il componente singleton sia disponibile in un test Hilt.
Scopri di più su @EarlyEntryPoint nella documentazione di Hilt.
Risorse aggiuntive
Per scoprire di più sui test, consulta le seguenti risorse aggiuntive: