Jedną z zalet korzystania z ramek wstrzykiwania zależności, takich jak Hilt, jest ułatwienie testowania kodu.
Testy jednostkowe
Hilt nie jest potrzebny do testów jednostkowych, ponieważ podczas testowania klasy, która korzysta z wstrzyknięcia konstruktora, nie musisz tworzyć instancji tej klasy za pomocą Hilta. Zamiast tego możesz bezpośrednio wywołać konstruktor klasy, przekazując fałszywe lub mock zależności, tak jak w przypadku, gdy konstruktor nie jest opatrzony adnotacjami:
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 w taki sam sposób, 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 testowania
Aby używać Hilta w testach, dodaj do projektu zależność hilt-android-testing
:
Odlotowe
dependencies { // For Robolectric tests. testImplementation 'com.google.dagger:hilt-android-testing:2.51.1' // ...with Kotlin. kaptTest 'com.google.dagger:hilt-android-compiler:2.51.1' // ...with Java. testAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.51.1' // For instrumented tests. androidTestImplementation 'com.google.dagger:hilt-android-testing:2.51.1' // ...with Kotlin. kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.51.1' // ...with Java. androidTestAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.51.1' }
Kotlin
dependencies { // For Robolectric tests. testImplementation("com.google.dagger:hilt-android-testing:2.51.1") // ...with Kotlin. kaptTest("com.google.dagger:hilt-android-compiler:2.51.1") // ...with Java. testAnnotationProcessor("com.google.dagger:hilt-android-compiler:2.51.1") // For instrumented tests. androidTestImplementation("com.google.dagger:hilt-android-testing:2.51.1") // ...with Kotlin. kaptAndroidTest("com.google.dagger:hilt-android-compiler:2.51.1") // ...with Java. androidTestAnnotationProcessor("com.google.dagger:hilt-android-compiler:2.51.1") }
Konfiguracja testu interfejsu
Każdy test UI, który korzysta z Hilt, musi być opatrzony adnotacjami @HiltAndroidTest
. Ta adnotacja odpowiada za generowanie komponentów Hilta dla każdego testu.
Musisz też dodać HiltAndroidRule
do klasy testowej. Zarządza stanem komponentów i służy do wstrzykiwania tych danych 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 wiedzieć, jak działa klasa Application
, którą Hilt automatycznie dla Ciebie generuje.
Aplikacja testowa
Testy z instrumentacją, które korzystają z Hilt, muszą być wykonywane w obiekcie Application
obsługującym Hilt. Biblioteka udostępnia funkcje HiltTestApplication
do wykorzystania 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 była uruchamiana w testach instrumentalnych lub testach Robolectric. Poniższe instrukcje nie dotyczą tylko Hilta, ale są ogólnymi wskazówkami dotyczącymi określania niestandardowej aplikacji do uruchamiania w ramach testów.
Konfigurowanie aplikacji testowej w testach z użyciem instrumentacji
Aby używać aplikacji testowej Hilt w testach z użyciem instrumentacji, musisz skonfigurować nowego wykonawcę testów. Dzięki temu Hilt będzie działać we wszystkich testach z instrumentacją w Twoim projekcie. Wykonaj te czynności:
- Utwórz klasę niestandardową rozszerzającą klasę
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 tego testującego w pliku Gradle zgodnie z opisem w przewodniku po testach jednostkowych z użyciem pomiarów. Pamiętaj, aby użyć pełnej ścieżki klasy:
Groovy
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" } }
Ustaw aplikację testową w testach Robolectric
Jeśli do testowania warstwy UI używasz Robolectric, możesz określić, której aplikacji użyć w pliku robolectric.properties
:
application = dagger.hilt.android.testing.HiltTestApplication
Możesz też skonfigurować aplikację w ramach każdego testu z osobna, używając adnotacji @Config
w 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:
Groovy
hilt { enableTransformForLocalTests = true }
Kotlin
hilt { enableTransformForLocalTests = true }
Więcej informacji o enableTransformForLocalTests
znajdziesz w dokumentacji Hiltu.
Funkcje testowania
Gdy Hilt będzie 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 pola @Inject
do wstrzyknięcia pól. Aby poprosić Hilt o wypełnienie pól @Inject
, wywołaj hiltRule.inject()
.
Zobacz przykład testu zinstruowanego:
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 chcesz wstrzykiwać fałszywą lub pozorowaną instancję zależności, musisz poinstruować Hilt, aby nie używał powiązania użytego w kodzie produkcyjnym i zamiast tego używało innego. Aby zastąpić powiązanie, musisz zastąpić moduł zawierający powiązanie modułem testowym zawierającym powiązania, których chcesz użyć w teście.
Załóżmy na przykład, że kod produkcyjny deklaruje ograniczenie dotyczące 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ść i dodaj do niej adnotację @TestInstallIn
. Zamiast tego do wszystkich testów w tym folderze jest wstrzykiwana 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 pojedynczym teście
Aby zastąpić powią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.
W przykładzie AnalyticsService
z poprzedniej wersji zacznij od poinformowania Hilta o ignorowaniu modułu produkcyjnego za pomocą adnotacji @UninstallModules
w klasie testowej:
Kotlin
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsActivityTest { ... }
Java
@UninstallModules(AnalyticsModule.class) @HiltAndroidTest public final class SettingsActivityTest { ... }
Następnie musisz wymienić oprawę. 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 ); } ... }
Spowoduje to zastąpienie tylko powiązania dla jednej klasy testów. Jeśli chcesz zastąpić wiązanie we wszystkich klasach testu, użyj adnotacji @TestInstallIn
z sekcji powyżej. Możesz też umieścić testowanie w module test
w przypadku testów Robolectric lub w module androidTest
w przypadku testów z użyciem instrumentacji.
Zalecamy, aby w miarę możliwości używać @TestInstallIn
.
Powiązanie nowych wartości
Użyj adnotacji @BindValue
, aby łatwo powiązać pola testu z grafem zależności Hilt. Dodaj adnotację do pola za pomocą @BindValue
, aby powiązać je w ramach zadeklarowanego typu pola ze wszystkimi kwalifikatorami występującymi w tym polu.
W przykładzie AnalyticsService
możesz zastąpić AnalyticsService
wartością fałszywą, 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ąpienie wiązania, jak i odwoływanie się do niego w teście, ponieważ umożliwia wykonanie obu tych czynności jednocześnie.
@BindValue
działa z kwalifikatorami i innymi adnotacjami testowymi. Jeśli np. używasz bibliotek testowych, takich jak Mockito, w teście Robolectric możesz wykorzystać te biblioteki 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ć wiązanie wieloelementowe, możesz użyć adnotacji @BindValueIntoSet
i @BindValueIntoMap
zamiast @BindValue
. @BindValueIntoMap
wymaga również adnotacji pola za pomocą adnotacji klucza mapy.
Przypadki szczególne
Hilt udostępnia też funkcje, które ułatwiają obsługę niestandardowych przypadków użycia.
Niestandardowa aplikacja do testów
Jeśli nie możesz użyć HiltTestApplication
, ponieważ aplikacja testowa musi rozszerzać inną aplikację, dodaj adnotację do nowej klasy lub interfejsu za pomocą @CustomTestApplication
, podają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 obiekt Application
o nazwie HiltTestApplication_Application
, który rozszerza klasę BaseApplication
. Zazwyczaj nazwa wygenerowanej aplikacji to nazwa opatrzonej adnotacjami klasy z dodaną końcówką _Application
. Wygenerowaną aplikację testową Hilt należy skonfigurować tak, aby była uruchamiana w testach z użyciem instrumentacji lub testach Robolectric zgodnie z opisem w aplikacji testowej.
Wiele obiektów TestRule w testach z użyciem instrumentacji
Jeśli w testach masz inne obiekty TestRule
, możesz na kilka sposobów zadbać o to, aby wszystkie reguły działały razem.
Reguły możesz owinąć 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żyć obu reguł na tym samym poziomie, o ile reguła HiltAndroidRule
zostanie wykonana jako pierwsza. Określ kolejność wykonania, używając atrybutu order
w adnotacji @Rule
. Ta metoda działa tylko w wersji JUnit 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ć funkcji launchFragmentInContainer
z biblioteki androidx.fragment:fragment-testing
w Hilt, ponieważ korzysta ona z aktywności, która nie jest opatrzona adnotacjami @AndroidEntryPoint
.
Zamiast tego użyj kodu launchFragmentInHiltContainer
z repozytorium GitHub architecture-samples
.
Używanie punktu wejścia przed udostępnieniem komponentu singleton
Adnotacja @EarlyEntryPoint
stanowi furtkę awaryjnego wyjścia, gdy punkt wejścia Hilta musi zostać utworzony, zanim komponent singleton będzie dostępny w teście Hilta.
Więcej informacji o @EarlyEntryPoint
znajdziesz w dokumentacji Hilt.