Jedną z zalet korzystania z platform wstrzykiwania zależności, takich jak Hilt, jest to, że ułatwiają one testowanie kodu.
Testy jednostkowe
Hilt nie jest wymagany w przypadku testów jednostkowych, ponieważ podczas testowania klasy, która korzysta z wstrzykiwania przez konstruktor, nie musisz używać Hilt do utworzenia instancji tej klasy. Zamiast tego możesz bezpośrednio wywołać konstruktor klasy, przekazując fałszywe lub pozorowane zależności, tak jak w przypadku, gdyby konstruktor nie był oznaczony adnotacją:
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(...); } }
Testy kompleksowe
W przypadku testów integracji Hilt wstrzykuje zależności tak samo jak w kodzie produkcyjnym. Testowanie za pomocą Hilt nie wymaga konserwacji, ponieważ Hilt automatycznie generuje nowy zestaw komponentów dla każdego testu.
Dodawanie zależności testowych
Aby używać Hilt w testach, dodaj do projektu zależność hilt-android-testing:
Dynamiczny
dependencies { // For Robolectric tests. testImplementation 'com.google.dagger:hilt-android-testing:2.57.1' // ...with Kotlin. kaptTest 'com.google.dagger:hilt-android-compiler:2.57.1' // ...with Java. testAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.57.1' // For instrumented tests. androidTestImplementation 'com.google.dagger:hilt-android-testing:2.57.1' // ...with Kotlin. kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.57.1' // ...with Java. androidTestAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.57.1' }
Kotlin
dependencies { // For Robolectric tests. testImplementation("com.google.dagger:hilt-android-testing:2.57.1") // ...with Kotlin. kaptTest("com.google.dagger:hilt-android-compiler:2.57.1") // ...with Java. testAnnotationProcessor("com.google.dagger:hilt-android-compiler:2.57.1") // For instrumented tests. androidTestImplementation("com.google.dagger:hilt-android-testing:2.57.1") // ...with Kotlin. kaptAndroidTest("com.google.dagger:hilt-android-compiler:2.57.1") // ...with Java. androidTestAnnotationProcessor("com.google.dagger:hilt-android-compiler:2.57.1") }
Konfiguracja testu interfejsu
Każdy test interfejsu, który korzysta z Hilt, musi być oznaczony adnotacją @HiltAndroidTest. Ta adnotacja odpowiada za generowanie komponentów Hilt dla każdego testu.
Musisz też dodać HiltAndroidRule do klasy testowej. Zarządza ona stanem komponentów i służy do wstrzykiwania w teście:
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. }
Następnie test musi znać klasę Application, którą Hilt automatycznie generuje.
Testuj aplikację
Testy instrumentowane, które korzystają z Hilt, musisz wykonywać w obiekcie Application, który obsługuje Hilt. Biblioteka udostępnia HiltTestApplication do użycia w testach.
Jeśli testy wymagają innej aplikacji podstawowej, zapoznaj się z sekcją dotyczącą aplikacji niestandardowej na
potrzeby testów.
Musisz ustawić aplikację testową tak, aby działała w testach instrumentowanych lub testach Robolectric. Poniższe instrukcje nie dotyczą konkretnie Hilt, ale zawierają ogólne wskazówki dotyczące określania aplikacji niestandardowej, która ma być uruchamiana w testach.
Ustawianie aplikacji testowej w testach instrumentowanych
Aby używać aplikacji testowej Hilt w instrumentowanych testach, musisz skonfigurować nowy program do uruchamiania testów. Dzięki temu Hilt będzie działać we wszystkich testach instrumentowanych w projekcie. Wykonaj te czynności:
- Utwórz klasę niestandardową, która rozszerza
AndroidJUnitRunnerw folderzeandroidTest. - Zastąp funkcję
newApplicationi przekaż nazwę wygenerowanej aplikacji testowej Hilt.
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); } }
Następnie skonfiguruj ten program do uruchamiania testów w pliku Gradle zgodnie z opisem w przewodniku po testach jednostkowych instrumentowanych. Upewnij się, że używasz pełnej ścieżki klasy:
Dynamiczny
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" } }
Ustawianie aplikacji testowej w testach Robolectric
Jeśli używasz Robolectric do testowania warstwy interfejsu, możesz określić, której aplikacji używać, w pliku robolectric.properties:
application = dagger.hilt.android.testing.HiltTestApplication
Możesz też skonfigurować aplikację w każdym teście osobno, używając adnotacji @Config 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. }
Jeśli używasz wtyczki Androida do obsługi Gradle w wersji niższej niż 4.2, włącz przekształcanie klas @AndroidEntryPoint w lokalnych testach jednostkowych, stosując tę konfigurację w pliku build.gradle modułu:
Dynamiczny
hilt { enableTransformForLocalTests = true }
Kotlin
hilt { enableTransformForLocalTests = true }
Więcej informacji o enableTransformForLocalTests znajdziesz w dokumentacji
Hilt.
Funkcje testowania
Gdy Hilt jest gotowy do użycia w testach, możesz użyć kilku funkcji, aby dostosować proces testowania.
Wstrzykiwanie typów w testach
Aby wstrzyknąć typy do testu, użyj @Inject do wstrzykiwania przez pola. Aby poinformować Hilt, że ma wypełnić pola @Inject, wywołaj hiltRule.inject().
Oto przykład testu instrumentowanego:
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. } }
Zastępowanie powiązania
Jeśli musisz wstrzyknąć fałszywą lub pozorowaną instancję zależności, musisz poinformować Hilt, aby nie używał powiązania, którego używał w kodzie produkcyjnym, i zamiast tego używał innego. Aby zastąpić powiązanie, musisz zastąpić moduł, który zawiera powiązanie, modułem testowym, który zawiera powiązania, których chcesz użyć w teście.
Załóżmy na przykład, że kod produkcyjny deklaruje powiązanie z AnalyticsService w ten sposób:
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 ); }
Aby zastąpić powiązanie AnalyticsService w testach, utwórz nowy moduł Hilt w folderze test lub androidTest z fałszywą zależnością i oznacz go adnotacją @TestInstallIn. We wszystkich testach w tym folderze zostanie wstrzyknięta fałszywa zależność.
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 ); }
Zastępowanie powiązania w jednym teście
Aby zastąpić powiązanie w jednym teście zamiast we wszystkich testach, odinstaluj moduł Hilt z testu za pomocą adnotacji @UninstallModules i utwórz nowy moduł testowy w teście.
W przykładzie AnalyticsService z poprzedniej wersji zacznij od poinformowania Hilt, aby ignorował moduł produkcyjny, używając adnotacji @UninstallModules w klasie testowej:
Kotlin
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsActivityTest { ... }
Java
@UninstallModules(AnalyticsModule.class) @HiltAndroidTest public final class SettingsActivityTest { ... }
Następnie musisz zastąpić powiązanie. Utwórz nowy moduł w klasie testowej, który definiuje powiązanie testowe:
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 ); } ... }
Spowoduje to zastąpienie powiązania tylko w przypadku jednej klasy testowej. Jeśli chcesz zastąpić powiązanie we wszystkich klasach testowych, użyj adnotacji @TestInstallIn z sekcji powyżej. Możesz też umieścić powiązanie testowe w module test w przypadku testów Robolectric lub w module androidTest w przypadku testów instrumentowanych.
Zalecamy, aby w miarę możliwości używać @TestInstallIn.
Powiązanie nowych wartości
Użyj adnotacji @BindValue, aby łatwo powiązać pola w teście z grafem zależności Hilt. Oznacz pole adnotacją @BindValue, a zostanie ono powiązane zadeklarowanym typem pola z kwalifikatorami, które są obecne w tym polu.
W przykładzie AnalyticsService możesz zastąpić AnalyticsService fałszywą wartością, używając @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(); ... }
Upraszcza to zarówno zastępowanie powiązania, jak i odwoływanie się do niego w teście, ponieważ możesz zrobić to jednocześnie.
@BindValue działa z kwalifikatorami i innymi adnotacjami testowymi. Jeśli na przykład
używasz bibliotek testowych, takich jak
Mockito, możesz użyć jej w teście
Robolectric w ten sposób:
Kotlin
... class SettingsActivityTest { ... @BindValue @ExampleQualifier @Mock lateinit var qualifiedVariable: ExampleCustomType // Robolectric tests here }
Java
... class SettingsActivityTest { ... @BindValue @ExampleQualifier @Mock ExampleCustomType qualifiedVariable; // Robolectric tests here }
Jeśli musisz dodać powiązanie wielokrotne,
możesz użyć adnotacji @BindValueIntoSet i @BindValueIntoMap zamiast
@BindValue. @BindValueIntoMap wymaga też oznaczenia pola adnotacją klucza mapy.
Przypadki szczególne
Hilt udostępnia też funkcje obsługujące niestandardowe przypadki użycia.
Aplikacja niestandardowa na potrzeby testów
Jeśli nie możesz użyć HiltTestApplication, ponieważ aplikacja testowa musi rozszerzać inną aplikację, oznacz nową klasę lub interfejs adnotacją @CustomTestApplication, przekazując wartość klasy bazowej, którą ma rozszerzać wygenerowana aplikacja Hilt.
@CustomTestApplication wygeneruje klasę Application gotową do testowania
za pomocą Hilt, która rozszerza aplikację przekazaną jako parametr.
Kotlin
@CustomTestApplication(BaseApplication::class) interface HiltTestApplication
Java
@CustomTestApplication(BaseApplication.class) interface HiltTestApplication { }
W tym przykładzie Hilt generuje Application o nazwie
HiltTestApplication_Application która rozszerza klasę BaseApplication. Ogólnie rzecz biorąc, nazwa wygenerowanej aplikacji to nazwa klasy oznaczonej adnotacją z dopiskiem _Application. Musisz ustawić wygenerowaną aplikację testową Hilt tak, aby działała w testach instrumentowanych lub testach Robolectric zgodnie z opisem w sekcji Testuj aplikację.
Wiele obiektów TestRule w teście instrumentowanym
Jeśli w teście masz inne obiekty TestRule, istnieje kilka sposobów, aby zapewnić, że wszystkie reguły będą działać razem.
Możesz połączyć reguły w ten sposób:
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. }
Możesz też używać obu reguł na tym samym poziomie, o ile HiltAndroidRule jest wykonywana jako pierwsza. Określ kolejność wykonywania za pomocą atrybutu order w adnotacji @Rule. Działa to tylko w JUnit w wersji 4.13 lub nowszej:
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
Nie można używać launchFragmentInContainer z biblioteki androidx.fragment:fragment-testing z Hilt, ponieważ opiera się ona na aktywności, która nie jest oznaczona adnotacją @AndroidEntryPoint.
Zamiast tego użyj
launchFragmentInHiltContainer
kodu z
architecture-samples GitHub
repozytorium.
Używanie punktu wejścia, zanim komponent singleton będzie dostępny
Adnotacja @EarlyEntryPoint zapewnia możliwość utworzenia punktu wejścia Hilt, zanim komponent singleton będzie dostępny w teście Hilt.
Więcej informacji o @EarlyEntryPoint znajdziesz w
dokumentacji Hilt.