Leitfaden für Hilt-Tests

Einer der Vorteile von Abhängigkeitsinjektions-Frameworks wie Hilt besteht darin, dass das Testen Ihres Codes erleichtert wird.

Unit tests

Für Einheitentests ist Hilt nicht erforderlich, da Sie beim Testen einer Klasse, die Konstruktor-Injektion verwendet, Hilt nicht verwenden müssen, um diese Klasse zu instanziieren. Stattdessen können Sie direkt einen Klassenkonstruktor aufrufen, indem Sie fiktive oder simulierte Abhängigkeiten übergeben, so wie Sie es tun würden, 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

Bei Integrationstests werden Abhängigkeiten durch Hilt wie in Ihrem Produktionscode eingeschleust. Tests mit Hilt sind nicht wartungspflichtig, da Hilt für jeden Test automatisch einen neuen Satz von Komponenten generiert.

Testabhängigkeiten hinzufügen

Fügen Sie die Abhängigkeit hilt-android-testing in Ihr Projekt ein, um Hilt in Ihren Tests zu verwenden:

Groovig

dependencies {
    // For Robolectric tests.
    testImplementation 'com.google.dagger:hilt-android-testing:2.44'
    // ...with Kotlin.
    kaptTest 'com.google.dagger:hilt-android-compiler:2.44'
    // ...with Java.
    testAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.44'


    // For instrumented tests.
    androidTestImplementation 'com.google.dagger:hilt-android-testing:2.44'
    // ...with Kotlin.
    kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.44'
    // ...with Java.
    androidTestAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.44'
}

Kotlin

dependencies {
    // For Robolectric tests.
    testImplementation("com.google.dagger:hilt-android-testing:2.44")
    // ...with Kotlin.
    kaptTest("com.google.dagger:hilt-android-compiler:2.44")
    // ...with Java.
    testAnnotationProcessor("com.google.dagger:hilt-android-compiler:2.44")


    // For instrumented tests.
    androidTestImplementation("com.google.dagger:hilt-android-testing:2.44")
    // ...with Kotlin.
    kaptAndroidTest("com.google.dagger:hilt-android-compiler:2.44")
    // ...with Java.
    androidTestAnnotationProcessor("com.google.dagger:hilt-android-compiler:2.44")
}

UI-Test einrichten

Sie müssen jeden UI-Test, der Hilt verwendet, mit @HiltAndroidTest annotieren. Diese Annotation ist für das Generieren der Hilt-Komponenten für jeden Test verantwortlich.

Außerdem musst du die HiltAndroidRule zur Testklasse hinzufügen. Sie verwaltet den Status der Komponenten und wird verwendet, um bei Ihrem Test eine Injektion durchzuführen:

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 der Test die Klasse Application kennen, die Hilt automatisch für Sie generiert.

Testanwendung

Sie müssen instrumentierte Tests mit Hilt in einem Application-Objekt ausführen, das Hilt unterstützt. Die Bibliothek stellt HiltTestApplication zur Verwendung in Tests bereit. Wenn für Ihre Tests eine andere Basisanwendung erforderlich ist, finden Sie weitere Informationen unter 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 bezieht sich nicht speziell auf Hilt, sondern stellt allgemeine Richtlinien zum Angeben einer benutzerdefinierten Anwendung zur Ausführung in Tests dar.

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. Gehen Sie dazu so vor:

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

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

Konfiguriere diesen Test-Runner als Nächstes in deiner Gradle-Datei, wie in der Anleitung zum Testen instrumentierter Einheiten beschrieben. Achten Sie darauf, den vollständigen Klassenpfad zu verwenden:

Groovig

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 du Robolectric zum Testen der UI-Ebene verwendest, kannst du in der Datei robolectric.properties angeben, welche Anwendung verwendet werden soll:

application = dagger.hilt.android.testing.HiltTestApplication

Alternativ können Sie die Anwendung mit der Annotation @Config von Robolectric für jeden Test einzeln konfigurieren:

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 ältere Version des Android-Gradle-Plug-ins als 4.2 verwenden, aktivieren Sie die transformierenden @AndroidEntryPoint-Klassen in lokalen Einheitentests. Wenden Sie dazu die folgende Konfiguration in der Datei build.gradle Ihres Moduls an:

Groovig

hilt {
    enableTransformForLocalTests = true
}

Kotlin

hilt {
    enableTransformForLocalTests = true
}

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

Testfunktionen

Sobald Hilt in Ihren Tests einsatzbereit ist, können Sie den Testprozess mit verschiedenen Funktionen anpassen.

Typen in Tests einschleusen

Verwende @Inject für die Feldeinschleusung, um Typen in einen Test einzufügen. Rufen Sie hiltRule.inject() auf, um Hilt anzuweisen, die @Inject-Felder auszufüllen.

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 fiktive oder fiktive Instanz einer Abhängigkeit injizieren müssen, müssen Sie Hilt anweisen, nicht die im Produktionscode verwendete Bindung, sondern eine andere zu verwenden. 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 so eine Bindung für AnalyticsService 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 Bindung AnalyticsService in Tests ersetzen möchten, erstellen Sie im Ordner test oder androidTest ein neues Hilt-Modul mit der fiktiven Abhängigkeit und annotieren Sie es mit @TestInstallIn. Alle Tests in diesem Ordner werden stattdessen mit der fiktiven Abhängigkeit injiziert.

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

Bindung in einem einzelnen Test ersetzen

Wenn Sie eine Bindung in einem einzelnen Test statt in allen Tests ersetzen möchten, deinstallieren Sie mithilfe der Annotation @UninstallModules ein Hilt-Modul aus einem Test und erstellen Sie innerhalb des Tests ein neues Testmodul.

Gemäß dem AnalyticsService-Beispiel aus der vorherigen Version weisen Sie Hilt zuerst an, das Produktionsmodul zu ignorieren. Verwenden Sie dazu die Annotation @UninstallModules in der Testklasse:

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 ein neues Modul innerhalb der Testklasse, 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
    );
  }
  ...
}

Dies ersetzt nur die Bindung für eine einzelne Testklasse. Wenn Sie die Bindung für alle Testklassen ersetzen möchten, verwenden Sie die Annotation @TestInstallIn aus dem obigen Abschnitt. Alternativ können Sie die Testbindung im Modul test für Robolectric-Tests oder in das Modul androidTest für instrumentierte Tests einfügen. Wir empfehlen, nach Möglichkeit @TestInstallIn zu verwenden.

Neue Werte binden

Mit der Annotation @BindValue können Sie Felder aus Ihrem Test ganz einfach an das Hilt-Abhängigkeitsdiagramm binden. Wenn Sie ein Feld mit @BindValue annotieren, wird es an den deklarierten Feldtyp mit allen Qualifizierern gebunden, die für dieses Feld vorhanden sind.

Im AnalyticsService-Beispiel können Sie AnalyticsService mithilfe von @BindValue durch eine Fälschung ersetzen:

Kotlin

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

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

  ...
}

Java

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

  @BindValue AnalyticsService analyticsService = FakeAnalyticsService();

  ...
}

Dies vereinfacht das Ersetzen einer Bindung und den Verweis auf eine Bindung in Ihrem Test, da Sie beides gleichzeitig tun können.

@BindValue funktioniert mit Qualifier und anderen Testanmerkungen. Wenn Sie beispielsweise Testbibliotheken wie Mockito verwenden, können Sie sie in einem Robolectric-Test so 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 Multibindung hinzufügen möchten, können Sie die Annotationen @BindValueIntoSet und @BindValueIntoMap anstelle von @BindValue verwenden. Bei @BindValueIntoMap müssen Sie das Feld außerdem mit einer Annotation für Kartenschlüssel annotieren.

Besondere Fälle

Hilt bietet auch Funktionen für nicht standardmäßige 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. Übergeben Sie dabei den Wert der Basisklasse, die die generierte Hilt-Anwendung erweitern soll.

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

Kotlin

@CustomTestApplication(BaseApplication::class)
interface HiltTestApplication

Java

@CustomTestApplication(BaseApplication.class)
interface HiltTestApplication { }

Im Beispiel generiert Hilt einen Application namens HiltTestApplication_Application, der die Klasse BaseApplication erweitert. Im Allgemeinen ist der Name der generierten Anwendung der Name der annotierten Klasse, an die _Application angehängt ist. 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 andere TestRule-Objekte in Ihrem Test enthalten sind, gibt es mehrere Möglichkeiten, dafür zu sorgen, dass alle Regeln zusammenwirken.

Sie können die Regeln wie folgt 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 Annotation @Rule an. Dies funktioniert nur in 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

launchFragmentInContainer aus der Bibliothek androidx.fragment:fragment-testing kann nicht mit Hilt verwendet werden, da es auf einer Aktivität basiert, die nicht mit @AndroidEntryPoint annotiert ist.

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

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

Die Annotation @EarlyEntryPoint dient als 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 Dokumentation zu Hilt.