Jedną z zalet korzystania ze platform wstrzykiwania zależności takich jak Hilt jest to, że testowanie kodu jest łatwiejsze.
Testy jednostkowe
Hilt nie jest potrzebny w przypadku testów jednostkowych, ponieważ w przypadku testowania klasy korzystającej z wstrzykiwania konstruktora nie trzeba używać Hilt do utworzenia instancji tej klasy. Zamiast tego możesz bezpośrednio wywoływać konstruktor klas, przekazując fałszywe lub pozorowane zależności, tak jak gdyby nie było do niego adnotacji:
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(...); } }
Kompleksowe testy
W przypadku testów integracji Hilt wprowadza zależności tak samo jak w kodzie produkcyjnym. Testowanie za pomocą Hilt nie wymaga konserwacji, ponieważ Hilt automatycznie generuje nowy zestaw komponentów do każdego testu.
Dodawanie zależności testowych
Aby użyć Hilt w testach, umieść w projekcie zależność hilt-android-testing
:
Odlotowy
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") }
Konfiguracja testu interfejsu użytkownika
Do każdego testu interfejsu, który używa Hilt, musisz dodać adnotacje z atrybutem @HiltAndroidTest
. Odpowiada ona za generowanie komponentów Hilt dla każdego testu.
Musisz też dodać do klasy testowej HiltAndroidRule
. Służy on do zarządzania stanem komponentów i służy do wprowadzania w teście wstrzykiwania:
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óra jest wygenerowana automatycznie przez Hilt.
Aplikacja testowa
Musisz wykonać testy instrumentalne, które wykorzystują Hilt, w obiekcie Application
obsługującym Hilt. Biblioteka udostępnia HiltTestApplication
do użycia w testach.
Jeśli testy wymagają innej aplikacji podstawowej, zapoznaj się z artykułem Niestandardowa aplikacja do testów.
Musisz ustawić aplikację testową tak, aby uruchamiała się w testach instrumentalnych lub testach Roobolectric. Podane niżej instrukcje nie dotyczą Hilt, ale zawierają ogólne wskazówki dotyczące określania niestandardowej aplikacji uruchamianej w testach.
Ustawianie aplikacji testowej w testach instrumentalnych
Aby używać aplikacji testowej Hilt w testach instrumentalnych, musisz skonfigurować nowy proces uruchamiający testy. Dzięki temu Hilt będzie pracować we wszystkich testach instrumentalnych w Twoim projekcie. Wykonaj te czynności:
- Utwórz klasę niestandardową, która rozszerza zakres
AndroidJUnitRunner
w folderzeandroidTest
. - Zastąp funkcję
newApplication
i 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 proces uruchamiający w pliku Gradle zgodnie z opisem w instrukcyjnym przewodniku po testowaniu jednostkowym. Użyj pełnej ścieżki klasy:
Odlotowy
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 do testowania warstwy UI używasz Robolectric, możesz w pliku robolectric.properties
określić aplikację, której chcesz użyć:
application = dagger.hilt.android.testing.HiltTestApplication
Możesz też skonfigurować aplikację oddzielnie w każdym teście, używając adnotacji @Config
firmy 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 starszej niż 4.2, włącz przekształcanie klas @AndroidEntryPoint
w testach jednostek lokalnych, stosując tę konfigurację w pliku build.gradle
modułu:
Odlotowy
hilt { enableTransformForLocalTests = true }
Kotlin
hilt { enableTransformForLocalTests = true }
Więcej informacji na temat metody enableTransformForLocalTests
znajdziesz w dokumentacji Hilt.
Funkcje testowania
Gdy firma Hilt będzie gotowa do użycia w testach, możesz dostosować proces testowania za pomocą kilku funkcji.
Typy wstrzykiwania w testach
Aby wstawić typy do testu, użyj polecenia @Inject
do wstrzykiwania pól. Aby polecić Hilt, że ma wypełnić pola @Inject
, wywołaj hiltRule.inject()
.
Oto przykład testu z instrumentacją:
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 wiązania
Jeśli chcesz wstrzykiwać fałszywą lub pozorowaną instancję zależności, poinformuj Hilt, że ma nie używać wiązania użytego w kodzie produkcyjnym, a zamiast tego użyć innego. Aby zastąpić wiązanie, które zawiera, modułem testowym zawierającym wiązania, których chcesz użyć w teście.
Załóżmy na przykład, że Twój kod produkcyjny deklaruje powiązanie 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ć w testach wiązanie AnalyticsService
, utwórz nowy moduł Hilt w folderze test
lub androidTest
z fałszywą zależność i dodaj do niego adnotację @TestInstallIn
. Zamiast tego do wszystkich testów w tym folderze stosowana jest 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 wiązania w jednym teście
Aby zastąpić wiązanie w jednym teście, a nie we wszystkich testach, odinstaluj moduł Hilt z testu za pomocą adnotacji @UninstallModules
i utwórz w teście nowy moduł testowy.
Korzystając z przykładu AnalyticsService
z poprzedniej wersji, zacznij od polecenia klientowi 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 trzeba zastąpić wiązanie. Utwórz w klasie testowej nowy moduł, który definiuje wiązanie testu:
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 ); } ... }
Zastępuje to tylko powiązanie dla jednej klasy testowej. Jeśli chcesz zastąpić wiązanie dla wszystkich klas testowych, użyj adnotacji @TestInstallIn
z sekcji powyżej. Wiązanie testowe możesz też umieścić w module test
(w przypadku testów robolectric) lub w module androidTest
(testy instrumentalne).
Zalecamy, aby w miarę możliwości używać elementu @TestInstallIn
.
Wiązanie nowych wartości
Użyj adnotacji @BindValue
, aby łatwo powiązać pola w teście z wykresem zależności Hilt. Jeśli dodasz do pola adnotacje o polu @BindValue
, zostanie ono powiązane z zadeklarowanym typem pola wszelkimi kwalifikatorami występującymi w jego przypadku.
W przykładzie AnalyticsService
możesz zastąpić AnalyticsService
fałszywą wartością, używając elementu @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ąpienie powiązania, jak i odwoływanie się do niego w teście, ponieważ umożliwia wykonywanie obu tych czynności w tym samym czasie.
Funkcja @BindValue
współpracuje z kwalifikatorami i innymi adnotacjami dotyczącymi testowania. Jeśli używasz np. bibliotek testowych takich jak Mockito, możesz użyć ich w teście Robolectric w taki 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 chcesz dodać powiązanie zbiorcze, możesz użyć adnotacji @BindValueIntoSet
i @BindValueIntoMap
zamiast @BindValue
. @BindValueIntoMap
wymaga dodania do pola adnotacji klucza mapy.
Przypadki szczególne
Hilt udostępnia też funkcje obsługujące niestandardowe przypadki użycia.
Niestandardowa aplikacja do testów
Jeśli nie możesz użyć HiltTestApplication
, ponieważ Twoja aplikacja testowa musi rozszerzyć inną aplikację, dodaj adnotacje do nowej klasy lub interfejsu za pomocą @CustomTestApplication
, przekazując wartość klasy podstawowej, którą wygenerowana aplikacja Hilt ma rozszerzać.
@CustomTestApplication
wygeneruje klasę Application
gotową do testowania za pomocą Hilt, która rozszerzy aplikację podaną przez Ciebie 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óry rozszerza klasę BaseApplication
. Ogólnie nazwa wygenerowanej aplikacji to nazwa klasy z adnotacjami uzupełnionej o _Application
. Musisz ustawić wygenerowaną aplikację do testów Hilt, która będzie uruchamiana w testach instruktażowych lub testach Robolectric zgodnie z opisem w sekcji Aplikacja testowa.
Wiele obiektów TestRule w teście z instrumentacją
Jeśli w teście masz inne obiekty TestRule
, możesz na kilka sposobów zapewnić, że wszystkie reguły będą ze sobą działać.
Możesz połączyć te 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 reguła HiltAndroidRule
jest wykonywana jako pierwsza. Określ zamówienie wykonania 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żyć elementu launchFragmentInContainer
z biblioteki androidx.fragment:fragment-testing
z Hilt, ponieważ bazuje on na działaniu, które nie ma adnotacji @AndroidEntryPoint
.
Użyj kodu launchFragmentInHiltContainer
z repozytorium architecture-samples
na GitHubie.
Użyj punktu wejścia, zanim komponent singleton będzie dostępny
Adnotacja @EarlyEntryPoint
zapewnia wyjście awaryjne, gdy trzeba utworzyć punkt wejścia Hilt, zanim komponent singleton stanie się dostępny w teście skoku.
Więcej informacji na temat właściwości @EarlyEntryPoint
znajdziesz w dokumentacji Hilt.