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 integracyjnych Hilt wstrzykuje zależności tak, 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
:
Groovy
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 jest używany do wstrzykiwania danych do testu:
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 HiltTestApplication
do użytku w testach.
Jeśli testy wymagają innej aplikacji podstawowej, zapoznaj się z artykułem Niestandardowa aplikacja do testów.
Aplikacja testowa musi być skonfigurowana tak, aby uruchamiać testy z użyciem instrumentacji lub testy 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 instrumentacji. Upewnij się, że używasz 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" } }
Konfigurowanie aplikacji testowej w testach Robolectric
Jeśli do testowania warstwy interfejsu użytkownika 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 Android Gradle w wersji niższej niż 4.2, włącz przekształcanie klas @AndroidEntryPoint
w lokalnych testach jednostkowych, stosując w pliku build.gradle
w module następującą konfigurację:
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.
Wstawianie typów w testach
Aby wstrzyknąć typy do testu, użyj pola @Inject
do wstrzyknięcia pól. Aby polecić Hilt wypełnienie pól @Inject
, wywołaj funkcję hiltRule.inject()
.
Oto przykład testu z użyciem instrumentacji:
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 wstrzyknąć fałszywy lub symulowany egzemplarz zależności, musisz powiedzieć Hiltowi, aby nie używał powiązania, którego używał w kodzie produkcyjnym, a zamiast tego użył 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ć w testach wiązanie AnalyticsService
, utwórz nowy moduł Hilt w folderze test
lub androidTest
z fałszywą zależnością i opatrz go 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 pojedynczym teście zamiast we wszystkich testach, odinstaluj moduł Hilta z testu za pomocą adnotacji @UninstallModules
i utwórz nowy moduł testu w ramach testu.
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 nowy moduł w klasie testu, który definiuje powią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
Za pomocą adnotacji @BindValue
możesz łatwo powiązać pola w teście z grafem zależności Hilta. Dodaj adnotację @BindValue
do pola, a zostanie ono powiązane z deklarowanym typem pola z wszelkimi obecnymi w nim kwalifikatorami.
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(); ... }
Dzięki temu możesz zastąpić powiązanie i odwoływać się do niego w testach 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ć ich 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ć 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 do obsługi 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 Application
o nazwie
HiltTestApplication_Application
, która rozszerza klasę BaseApplication
. Zazwyczaj nazwa wygenerowanej aplikacji to nazwa opatrzonej adnotacjami klasy z dołączonym znakiem _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ść wykonywania za pomocą 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ż opiera się ona na 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 Hiltu.