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:
- Erstellen Sie im Ordner
androidTest
eine benutzerdefinierte Klasse, dieAndroidJUnitRunner
erweitert. - Ü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.