Uno dei vantaggi dell'utilizzo di framework di dependency injection come Hilt è che semplifica il test del codice.
Test delle unità
Hilt non è necessario per i test delle unità, poiché quando testi una classe che utilizza la constructor injection, non devi utilizzare Hilt per creare un'istanza di quella classe. Invece, puoi chiamare direttamente un costruttore di classe passando dipendenze false o fittizie, proprio come faresti se il costruttore non fosse annotato:
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(...); } }
Test end-to-end
Per i test di integrazione, Hilt inserisce le dipendenze come farebbe nel codice di produzione. Il test con Hilt non richiede manutenzione perché Hilt genera automaticamente un nuovo set di componenti per ogni test.
Aggiungere dipendenze di test
Per utilizzare Hilt nei test, includi la dipendenza hilt-android-testing nel progetto:
Alla moda
dependencies { // For Robolectric tests. testImplementation 'com.google.dagger:hilt-android-testing:2.57.1' // ...with Kotlin. kaptTest 'com.google.dagger:hilt-android-compiler:2.57.1' // ...with Java. testAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.57.1' // For instrumented tests. androidTestImplementation 'com.google.dagger:hilt-android-testing:2.57.1' // ...with Kotlin. kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.57.1' // ...with Java. androidTestAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.57.1' }
Kotlin
dependencies { // For Robolectric tests. testImplementation("com.google.dagger:hilt-android-testing:2.57.1") // ...with Kotlin. kaptTest("com.google.dagger:hilt-android-compiler:2.57.1") // ...with Java. testAnnotationProcessor("com.google.dagger:hilt-android-compiler:2.57.1") // For instrumented tests. androidTestImplementation("com.google.dagger:hilt-android-testing:2.57.1") // ...with Kotlin. kaptAndroidTest("com.google.dagger:hilt-android-compiler:2.57.1") // ...with Java. androidTestAnnotationProcessor("com.google.dagger:hilt-android-compiler:2.57.1") }
kaptTest
o kaptAndroidTest per Kotlin oppure con testAnnotationProcessor o
androidTestAnnotationProcessor per Java.
Configurazione del test dell'UI
Devi annotare qualsiasi test dell'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'injection nel test:
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. }
Successivamente, il test deve conoscere la classe Application che Hilt genera automaticamente per te.
Applicazione di test
Devi eseguire i test strumentati che utilizzano Hilt in un oggetto Application che supporta Hilt. La libreria fornisce HiltTestApplication per l'utilizzo nei test.
Se i test richiedono un'applicazione di base diversa, consulta la sezione Applicazione personalizzata per
i test.
Devi impostare l'esecuzione dell'applicazione di test nei test strumentati 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.
Impostare l'applicazione di test nei test strumentati
Per utilizzare l'applicazione di test Hilt nei test strumentati, devi configurare un nuovo runner di test. In questo modo, Hilt funziona per tutti i test strumentati del progetto. Esegui i seguenti passaggi:
- Crea una classe personalizzata che estenda
AndroidJUnitRunnerin theandroidTestfolder. - Esegui l'override della funzione
newApplicatione passa il nome dell'applicazione di test Hilt generata.
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); } }
Successivamente, configura questo runner di test nel file Gradle come descritto nella guida ai test delle unità strumentate. Assicurati di utilizzare il classpath completo:
Alla moda
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" } }
Impostare l'applicazione di test nei test Robolectric
Se utilizzi Robolectric per testare il livello dell'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:
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. }
Se utilizzi una versione del plug-in Android per Gradle precedente alla 4.2, attiva la trasformazione delle classi @AndroidEntryPoint nei test delle unità locali applicando la seguente configurazione nel file build.gradle del modulo:
Alla moda
hilt { enableTransformForLocalTests = true }
Kotlin
hilt { enableTransformForLocalTests = true }
Ulteriori informazioni su enableTransformForLocalTests nella documentazione
di Hilt.
Testare le funzionalità
Una volta che Hilt è pronto per essere utilizzato nei test, puoi utilizzare diverse funzionalità per personalizzare la procedura di test.
Inserire i tipi nei test
Per inserire i tipi in un test, utilizza @Inject per la field injection. Per indicare a Hilt di popolare i campi @Inject, chiama hiltRule.inject().
Vedi il seguente esempio di test strumentato:
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. } }
Sostituire un'associazione
Se devi inserire un'istanza falsa o fittizia di una dipendenza, devi indicare a Hilt di non utilizzare l'associazione utilizzata nel codice di produzione e di utilizzarne un'altra. Per sostituire un'associazione, devi sostituire il modulo che contiene l'associazione 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:
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 ); }
Per sostituire l'associazione AnalyticsService nei test, crea un nuovo modulo Hilt nella cartella test o androidTest con la dipendenza fittizia e annotalo con @TestInstallIn. A tutti i test in questa cartella viene inserita la dipendenza fittizia.
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 ); }
Sostituire 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:
Kotlin
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsActivityTest { ... }
Java
@UninstallModules(AnalyticsModule.class) @HiltAndroidTest public final class SettingsActivityTest { ... }
Successivamente, devi sostituire l'associazione. Crea un nuovo modulo all'interno della classe di test che definisce l'associazione di test:
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 ); } ... }
In questo modo, l'associazione viene sostituita solo 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 strumentati.
È consigliabile utilizzare @TestInstallIn quando possibile.
Associare 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 di AnalyticsService, puoi sostituire AnalyticsService con un valore fittizio utilizzando @BindValue:
Kotlin
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsActivityTest { @BindValue @JvmField val analyticsService: AnalyticsService = FakeAnalyticsService() ... }
Java
@UninstallModules(AnalyticsModule.class) @HiltAndroidTest class SettingsActivityTest { @BindValue AnalyticsService analyticsService = FakeAnalyticsService(); ... }
In questo modo, puoi sostituire un'associazione e farvi riferimento nel test contemporaneamente.
@BindValue funziona con i qualificatori e altre annotazioni di test. Ad esempio,
se utilizzi librerie di test come
Mockito, puoi utilizzarla in un test
Robolectric come segue:
Kotlin
... class SettingsActivityTest { ... @BindValue @ExampleQualifier @Mock lateinit var qualifiedVariable: ExampleCustomType // Robolectric tests here }
Java
... class SettingsActivityTest { ... @BindValue @ExampleQualifier @Mock ExampleCustomType qualifiedVariable; // Robolectric tests here }
Se devi aggiungere un multibinding,
puoi utilizzare le annotazioni @BindValueIntoSet e @BindValueIntoMap al posto
di @BindValue. @BindValueIntoMap richiede di annotare anche 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.
Kotlin
@CustomTestApplication(BaseApplication::class) interface HiltTestApplication
Java
@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 a cui viene aggiunto _Application. Devi impostare l'esecuzione dell'applicazione di test Hilt generata nei test strumentati o nei
test Robolectric come descritto in Applicazione di test.
Più oggetti TestRule nel test strumentato
Se hai altri oggetti TestRule nel test, esistono diversi modi per assicurarti che tutte le regole funzionino insieme.
Puoi raggruppare le regole come segue:
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. }
In alternativa, puoi utilizzare entrambe le regole allo stesso livello, purché HiltAndroidRule venga eseguita per prima. Specifica l'ordine di esecuzione utilizzando l'attributo order nell'annotazione @Rule. Questa operazione funziona solo in JUnit versione 4.13 o successive:
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
Non è possibile utilizzare launchFragmentInContainer dalla libreria androidx.fragment:fragment-testing con Hilt, perché si basa su un'attività non annotata con @AndroidEntryPoint.
Utilizza invece il
architecture-samples codice dal
launchFragmentInHiltContainer repository GitHub.
Utilizzare un punto di ingresso prima che il componente singleton sia disponibile
L'annotazione @EarlyEntryPoint fornisce una soluzione alternativa quando è necessario creare un punto di ingresso Hilt prima che il componente singleton sia disponibile in un test Hilt.
Ulteriori informazioni su @EarlyEntryPoint nella
documentazione di Hilt.