Leitfaden für Hilt-Tests

Einer der Vorteile von Dependency Injection-Frameworks wie Hilt ist, dass sie das Testen von Code erleichtern.

Einheitentests

Hilt ist für Unit-Tests nicht erforderlich, da Sie beim Testen einer Klasse, die die Konstruktoreinjiektion verwendet, Hilt nicht verwenden müssen, um diese Klasse zu instanziieren. Stattdessen können Sie einen Klassenkonstruktor direkt aufrufen, indem Sie gefälschte oder Mock-Abhängigkeiten übergeben, genau wie wenn der Konstruktor nicht annotiert wäre:

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

End-to-End-Tests

Für Integrationstests fügt Hilt Abhängigkeiten ein, wie es auch in Ihrem Produktionscode der Fall wäre. Tests mit Hilt erfordern keine Wartung, da Hilt automatisch eine neue Gruppe von Komponenten für jeden Test generiert.

Testabhängigkeiten hinzufügen

Wenn Sie Hilt in Ihren Tests verwenden möchten, fügen Sie die Abhängigkeit hilt-android-testing in Ihr Projekt ein:

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-Testeinrichtung

Alle UI-Tests, in denen Hilt verwendet wird, müssen mit @HiltAndroidTest annotiert werden. Diese Anmerkung ist für die Generierung der Hilt-Komponenten für jeden Test verantwortlich.

Außerdem müssen Sie der Testklasse die HiltAndroidRule hinzufügen. Es verwaltet den Zustand der Komponenten und wird zum Ausführen von Injections in Ihrem Test verwendet:

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

Als Nächstes muss Ihr Test die Application-Klasse kennen, die Hilt automatisch für Sie generiert.

Test-App

Sie müssen instrumentierte Tests ausführen, die Hilt in einem Application-Objekt verwenden, das Hilt unterstützt. Die Bibliothek bietet HiltTestApplication für die Verwendung in Tests. Wenn für Ihre Tests eine andere Basisanwendung erforderlich ist, lesen Sie den Hilfeartikel Benutzerdefinierte Anwendung für Tests.

Sie müssen Ihre Testanwendung so einrichten, dass sie in Ihren instrumentierten Tests oder Robolectric-Tests ausgeführt wird. Die folgende Anleitung ist nicht Hilt-spezifisch, sondern enthält allgemeine Richtlinien zum Angeben einer benutzerdefinierten Anwendung, die in Tests ausgeführt werden soll.

Testanwendung in instrumentierten Tests festlegen

Wenn Sie die Hilt-Testanwendung in instrumentierten Tests verwenden möchten, müssen Sie einen neuen Test-Runner konfigurieren. Dadurch funktioniert Hilt für alle instrumentierten Tests in Ihrem Projekt. Führen Sie die folgenden Schritte aus:

  1. Erstellen Sie eine benutzerdefinierte Klasse, die AndroidJUnitRunner im Ordner androidTest erweitert.
  2. Überschreiben Sie die Funktion newApplication und geben Sie den Namen der generierten Hilt-Testanwendung ein.

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

Konfigurieren Sie diesen Test-Runner dann in Ihrer Gradle-Datei wie im Leitfaden für instrumentierte Unit-Tests beschrieben. Verwenden Sie den vollständigen Pfad:

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"
    }
}
Testanwendung in Robolectric-Tests festlegen

Wenn Sie Robolectric zum Testen Ihrer UI-Ebene verwenden, können Sie in der robolectric.properties-Datei angeben, welche Anwendung verwendet werden soll:

application = dagger.hilt.android.testing.HiltTestApplication

Alternativ können Sie die Anwendung für jeden Test einzeln konfigurieren, indem Sie die @Config-Anmerkung von Robolectric verwenden:

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

Wenn Sie eine Android Gradle-Plug-in-Version niedriger als 4.2 verwenden, aktivieren Sie die Transformation von @AndroidEntryPoint-Klassen in lokalen Unit-Tests, indem Sie die folgende Konfiguration in der build.gradle-Datei Ihres Moduls anwenden:

Groovy

hilt {
    enableTransformForLocalTests = true
}

Kotlin

hilt {
    enableTransformForLocalTests = true
}

Weitere Informationen zu enableTransformForLocalTests finden Sie in der Hilt-Dokumentation.

Testfunktionen

Sobald Hilt für Ihre Tests bereit ist, können Sie den Testprozess mithilfe verschiedener Funktionen anpassen.

Typen in Tests einschleusen

Wenn Sie Typen in einen Test einschleusen möchten, verwenden Sie @Inject für die Feldinjektion. Wenn Sie Hilt anweisen möchten, die @Inject-Felder auszufüllen, rufen Sie hiltRule.inject() auf.

Hier ein Beispiel für einen instrumentierten Test:

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

Bindung ersetzen

Wenn Sie eine gefälschte oder simulierte Instanz einer Abhängigkeit einschleusen möchten, müssen Sie Hilt anweisen, die Bindung, die im Produktionscode verwendet wird, nicht zu verwenden, sondern stattdessen eine andere. Wenn Sie eine Bindung ersetzen möchten, müssen Sie das Modul, das die Bindung enthält, durch ein Testmodul ersetzen, das die Bindungen enthält, die Sie im Test verwenden möchten.

Angenommen, in Ihrem Produktionscode wird eine Bindung für AnalyticsService so deklariert:

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

Wenn Sie die AnalyticsService-Bindung in Tests ersetzen möchten, erstellen Sie im Ordner test oder androidTest ein neues Hilt-Modul mit der gefälschten Abhängigkeit und kennzeichnen Sie es mit @TestInstallIn. Stattdessen wird die gefälschte Abhängigkeit in alle Tests in diesem Ordner eingefügt.

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

Eine Bindung in einem einzelnen Test ersetzen

Wenn Sie eine Bindung in einem einzelnen Test anstelle aller Tests ersetzen möchten, deinstallieren Sie ein Hilt-Modul aus einem Test mithilfe der Anmerkung @UninstallModules und erstellen Sie ein neues Testmodul im Test.

Ähnlich wie im AnalyticsService-Beispiel der vorherigen Version weisen Sie Hilt an, das Produktionsmodul zu ignorieren, indem Sie die @UninstallModules-Anmerkung in der Testklasse verwenden:

Kotlin

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

Java

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

Als Nächstes müssen Sie die Bindung ersetzen. Erstellen Sie in der Testklasse ein neues Modul, das die Testbindung definiert:

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

Dadurch wird nur die Bindung für eine einzelne Testklasse ersetzt. Wenn Sie die Bindung für alle Testklassen ersetzen möchten, verwenden Sie die @TestInstallIn-Anmerkung aus dem Abschnitt oben. Alternativ können Sie die Testbindung im Modul test für Robolectric-Tests oder im Modul androidTest für instrumentierte Tests platzieren. Wir empfehlen, nach Möglichkeit @TestInstallIn zu verwenden.

Neue Werte binden

Mit der Anmerkung @BindValue können Sie Felder in Ihrem Test ganz einfach an den Hilt-Abhängigkeitsgraphen binden. Wenn Sie ein Feld mit @BindValue annotieren, wird es mit allen für dieses Feld vorhandenen Qualifikationen unter dem angegebenen Feldtyp gebunden.

Im Beispiel für AnalyticsService können Sie AnalyticsService durch einen gefälschten Wert ersetzen, indem Sie @BindValue verwenden:

Kotlin

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

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

  ...
}

Java

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

  @BindValue AnalyticsService analyticsService = FakeAnalyticsService();

  ...
}

So können Sie sowohl eine Bindung ersetzen als auch auf eine Bindung in Ihrem Test verweisen.

@BindValue funktioniert mit Qualifikatoren und anderen Testanmerkungen. Wenn Sie beispielsweise Testbibliotheken wie Mockito verwenden, können Sie sie so in einem Robolectric-Test verwenden:

Kotlin

...
class SettingsActivityTest {
  ...

  @BindValue @ExampleQualifier @Mock
  lateinit var qualifiedVariable: ExampleCustomType

  // Robolectric tests here
}

Java

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

  // Robolectric tests here
}

Wenn Sie eine Mehrfachanbindung hinzufügen möchten, können Sie die Anmerkungen @BindValueIntoSet und @BindValueIntoMap anstelle von @BindValue verwenden. Für @BindValueIntoMap müssen Sie das Feld auch mit einer Kartenschlüsselanmerkung versehen.

Besondere Fälle

Hilt bietet auch Funktionen zur Unterstützung nicht standardmäßiger Anwendungsfälle.

Benutzerdefinierte Anwendung für Tests

Wenn Sie HiltTestApplication nicht verwenden können, weil Ihre Testanwendung eine andere Anwendung erweitern muss, annotieren Sie eine neue Klasse oder Schnittstelle mit @CustomTestApplication und geben Sie den Wert der Basisklasse an, die die generierte Hilt-Anwendung erweitern soll.

@CustomTestApplication generiert eine Application-Klasse, die mit Hilt zum Testen bereit ist und die Anwendung erweitert, die Sie als Parameter übergeben haben.

Kotlin

@CustomTestApplication(BaseApplication::class)
interface HiltTestApplication

Java

@CustomTestApplication(BaseApplication.class)
interface HiltTestApplication { }

In diesem Beispiel generiert Hilt eine Application namens HiltTestApplication_Application, die die Klasse BaseApplication erweitert. Im Allgemeinen ist der Name der generierten Anwendung der Name der annotieren Klasse mit dem Suffix _Application. Sie müssen die generierte Hilt-Testanwendung so einrichten, dass sie in Ihren instrumentierten Tests oder Robolectric-Tests ausgeführt wird, wie unter Testanwendung beschrieben.

Mehrere TestRule-Objekte in Ihrem instrumentierten Test

Wenn Sie andere TestRule-Objekte in Ihrem Test haben, gibt es mehrere Möglichkeiten, dafür zu sorgen, dass alle Regeln zusammen funktionieren.

Sie können die Regeln so zusammenfassen:

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

Alternativ können Sie beide Regeln auf derselben Ebene verwenden, solange HiltAndroidRule zuerst ausgeführt wird. Geben Sie die Ausführungsreihenfolge mit dem Attribut order in der Anmerkung @Rule an. Das funktioniert nur mit JUnit-Version 4.13 oder höher:

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

Es ist nicht möglich, launchFragmentInContainer aus der androidx.fragment:fragment-testing-Bibliothek mit Hilt zu verwenden, da es auf einer Aktivität basiert, die nicht mit @AndroidEntryPoint annotiert ist.

Verwenden Sie stattdessen den launchFragmentInHiltContainer-Code aus dem GitHub-Repository architecture-samples.

Einen Einstiegspunkt verwenden, bevor die Singleton-Komponente verfügbar ist

Die Anmerkung @EarlyEntryPoint bietet eine Ausstiegsmöglichkeit, wenn ein Hilt-Einstiegspunkt erstellt werden muss, bevor die Singleton-Komponente in einem Hilt-Test verfügbar ist.

Weitere Informationen zu @EarlyEntryPoint finden Sie in der Hilt-Dokumentation.