Einer der Vorteile von Abhängigkeitsinjektions-Frameworks wie Hilt besteht darin, dass sie das Testen Ihres Codes erleichtert.
Einheitentests
Für Einheitentests ist „Hilt“ nicht erforderlich, da Sie beim Testen einer Klasse, die eine Konstruktor-Injektion verwendet, nicht Hilt verwenden müssen, um diese Klasse zu instanziieren. Stattdessen können Sie einen Klassenkonstruktor direkt aufrufen, indem Sie fiktive oder simulierte Abhängigkeiten übergeben, so als ob der Konstruktor nicht annotiert werden würde:
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 fügt Hilt Abhängigkeiten so wie in Ihren Produktionscode ein. Tests mit Hilt sind nicht wartungsfrei, da Hilt für jeden Test automatisch einen neuen Satz von Komponenten 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:
Cool
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 alle UI-Tests, die Hilt verwenden, mit @HiltAndroidTest
annotieren. Diese Annotation ist für das Generieren der Hilt-Komponenten für jeden Test verantwortlich.
Außerdem musst du der Testklasse das HiltAndroidRule
hinzufügen. Sie verwaltet den Status der Komponenten und wird verwendet, um eine Injektion in Ihren Test 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 Ihr Test Informationen zur Klasse Application
haben, die Hilt automatisch für Sie generiert.
Anwendung testen
Sie müssen instrumentierte Tests ausführen, die Hilt in einem Application
-Objekt verwenden, 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 die 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 ist allgemeine Richtlinien zum Angeben einer benutzerdefinierten Anwendung für Tests.
Testanwendung in instrumentierten Tests festlegen
Wenn Sie die Hilt-Testanwendung in instrumentierten Tests verwenden möchten, müssen Sie einen neuen Test-Runner konfigurieren. Daher funktioniert Hilt für alle instrumentierten Tests in Ihrem Projekt. Gehen Sie dazu so vor:
- Erstellen Sie eine benutzerdefinierte Klasse, die
AndroidJUnitRunner
im OrdnerandroidTest
erweitert. - Überschreibe die
newApplication
-Funktion und übergib 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); } }
Konfigurieren Sie als Nächstes diesen Test-Runner in der Gradle-Datei, wie in der Anleitung zum Testen von instrumentierten Einheiten beschrieben. Achten Sie darauf, den vollständigen Klassenpfad zu verwenden:
Cool
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 Ihre UI-Ebene mit Robolectric testen, können Sie 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 ein Android-Gradle-Plug-in vor Version 4.2 verwenden, aktivieren Sie das Transformieren von @AndroidEntryPoint
-Klassen in lokalen Einheitentests. Wenden Sie dazu die folgende Konfiguration in der build.gradle
-Datei Ihres Moduls an:
Cool
hilt { enableTransformForLocalTests = true }
Kotlin
hilt { enableTransformForLocalTests = true }
Weitere Informationen zu enableTransformForLocalTests
finden Sie in der Hilt-Dokumentation.
Testfunktionen
Sobald Hilt für Ihre Tests einsatzbereit ist, können Sie den Testprozess mit verschiedenen Funktionen anpassen.
Injektionstypen in Tests
Verwenden Sie @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 Fake- oder Simulationsinstanz einer Abhängigkeit einfügen müssen, müssen Sie Hilt anweisen, nicht die Bindung aus dem Produktionscode zu verwenden, sondern stattdessen eine andere Bindung. Um eine Bindung zu ersetzen, müssen Sie das Modul, das die Bindung enthält, durch ein Testmodul mit den Bindungen ersetzen, die Sie im Test verwenden möchten.
Angenommen, Ihr Produktionscode deklariert so eine Bindung für AnalyticsService
:
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 fiktiven Abhängigkeit und annotieren Sie diese mit @TestInstallIn
. Alle Tests in diesem Ordner werden stattdessen mit der fiktiven Abhängigkeit eingeschleust.
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 ein Hilt-Modul aus einem Test mit der Annotation @UninstallModules
und erstellen Sie ein neues Testmodul innerhalb des Tests.
Folge dem AnalyticsService
-Beispiel aus der vorherigen Version und weist Hilt zuerst an, das Produktionsmodul zu ignorieren. Dazu verwendet er 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 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 Annotation @TestInstallIn
aus dem obigen Abschnitt. Alternativ können Sie die Testbindung in das Modul test
für Robolectric-Tests oder in das Modul androidTest
für instrumentierte Tests einfügen.
Es wird empfohlen, nach Möglichkeit @TestInstallIn
zu verwenden.
Neue Werte binden
Mit der Annotation @BindValue
können Sie Felder in Ihrem Test ganz einfach an das Hilt-Abhängigkeitsdiagramm binden. Wenn Sie ein Feld mit @BindValue
annotieren, wird es unter dem deklarierten Feldtyp mit allen Qualifizierern gebunden, die für dieses Feld vorhanden sind.
Im Beispiel AnalyticsService
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 sowohl das Ersetzen einer Bindung als auch das Verweisen auf eine Bindung in Ihrem Test, da Sie beide gleichzeitig ausführen können.
@BindValue
funktioniert mit Qualifizierern und anderen Testannotationen. Wenn Sie beispielsweise Testbibliotheken wie Mockito verwenden, können Sie sie so in einem robots.txt-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 Multibinding hinzufügen müssen, können Sie die Annotationen @BindValueIntoSet
und @BindValueIntoMap
anstelle von @BindValue
verwenden. Bei @BindValueIntoMap
müssen Sie das Feld außerdem mit einer Zuordnungsschlüssel-Annotation 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
und übergeben Sie den Wert der Basisklasse, die von der generierten Hilt-Anwendung erweitert werden soll.
@CustomTestApplication
generiert eine Application
-Klasse, die zum Testen mit Hilt 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 annotierten Klasse, an die _Application
angehängt wird. 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 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 ab JUnit-Version 4.13:
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
.
Verwenden Sie einen Einstiegspunkt, bevor die Singleton-Komponente verfügbar ist
Die Annotation @EarlyEntryPoint
bietet eine Ausstiegsmöglichkeit, wenn ein Hilt-Einstiegspunkt erstellt werden muss, bevor die Singleton-Komponente in einem Treffertest verfügbar ist.
Weitere Informationen zu @EarlyEntryPoint
finden Sie in der Hilt-Dokumentation.