Guide des tests Hilt

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é :

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(...);
  }
}

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 :

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")
}

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 :

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.
}

Ensuite, votre test doit connaître la classe Application générée automatiquement par Hilt.

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 :

  1. Créez une classe personnalisée qui étend AndroidJUnitRunner dans le dossier androidTest.
  2. Ignorez la fonction newApplication et indiquez le nom de l'application de test Hilt générée.

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);
  }
}

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 :

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"
    }
}
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 :

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.
}

Si vous utilisez une version du plug-in Android Gradle antérieure à la version 4.2, activez la transformation des classes @AndroidEntryPoint dans les tests unitaires locaux en appliquant la configuration suivante dans le fichier build.gradle de votre module :

Groovy

hilt {
    enableTransformForLocalTests = true
}

Kotlin

hilt {
    enableTransformForLocalTests = true
}

Pour en savoir plus sur enableTransformForLocalTests, consultez la documentation Hilt.

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 :

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.
  }
}

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 :

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
  );
}

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.

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
  );
}

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 :

Kotlin

@UninstallModules(AnalyticsModule::class)
@HiltAndroidTest
class SettingsActivityTest { ... }

Java

@UninstallModules(AnalyticsModule.class)
@HiltAndroidTest
public final class SettingsActivityTest { ... }

Vous devez ensuite remplacer la liaison. Créez un module dans la classe de test qui définit la liaison de 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
    );
  }
  ...
}

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 :

Kotlin

@UninstallModules(AnalyticsModule::class)
@HiltAndroidTest
class SettingsActivityTest {

  @BindValue @JvmField
  val analyticsService: AnalyticsService = FakeAnalyticsService()

  ...
}

Java

@UninstallModules(AnalyticsModule.class)
@HiltAndroidTest
class SettingsActivityTest {

  @BindValue 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 :

Kotlin

...
class SettingsActivityTest {
  ...

  @BindValue @ExampleQualifier @Mock
  lateinit var qualifiedVariable: ExampleCustomType

  // Robolectric tests here
}

Java

...
class SettingsActivityTest {
  ...
  @BindValue @ExampleQualifier @Mock ExampleCustomType qualifiedVariable;

  // 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.

Kotlin

@CustomTestApplication(BaseApplication::class)
interface HiltTestApplication

Java

@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

Si vous avez d'autres objets TestRule dans votre test, il existe plusieurs façons de s'assurer que toutes les règles fonctionnent ensemble.

Vous pouvez encapsuler les règles comme suit :

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.
}

Vous pouvez également utiliser les deux règles au même niveau tant que HiltAndroidRule s'exécute la première. Spécifiez l'ordre d'exécution à l'aide de l'attribut order dans l'annotation @Rule. Cette méthode ne fonctionne qu'à partir de la version 4.13 ou supérieur de JUnit :

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

Il n'est pas possible d'utiliser launchFragmentInContainer à partir de la bibliothèque androidx.fragment:fragment-testing avec Hilt, car il repose sur une activité qui n'est pas annotée avec @AndroidEntryPoint.

Utilisez plutôt le code launchFragmentInHiltContainer du dépôt GitHub architecture-samples.

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.