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 potrzebny w testach jednostkowych, ponieważ podczas testowania klasy, która korzysta z wstrzykiwania w konstruktorze, nie musisz używać Hilta do utworzenia jej instancji. 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ł opatrzony adnotacją:
@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(...) } }
To samo dotyczy klas ViewModel uzyskanych przez wywołanie hiltViewModel() w funkcjach kompozycyjnych. W testach jednostkowych konstruuj ViewModel bezpośrednio za pomocą obiektów zastępczych.
Więcej informacji o tym, jak stan przepływa z ViewModel do funkcji kompozycyjnych, znajdziesz w artykułach Stan i Jetpack Compose oraz Gdzie przenieść stan.
Testy kompleksowe
W przypadku testów integracyjnych 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ć Hilta w testach, dodaj do projektu zależność hilt-android-testing:
dependencies { // For Robolectric tests. testImplementation("com.google.dagger:hilt-android-testing:2.57.1") kspTest("com.google.dagger:hilt-android-compiler:2.57.1") // For instrumented tests. androidTestImplementation("com.google.dagger:hilt-android-testing:2.57.1") kspAndroidTest("com.google.dagger:hilt-android-compiler:2.57.1") // Compose UI test rule. androidTestImplementation("androidx.compose.ui:ui-test-junit4") debugImplementation("androidx.compose.ui:ui-test-manifest") }
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 stanem komponentów i służy do wstrzykiwania w teście:
@HiltAndroidTest class SettingsScreenTest { @get:Rule(order = 0) val hiltRule = HiltAndroidRule(this) @get:Rule(order = 1) val composeRule = createAndroidComposeRule<HiltTestActivity>() // Compose UI tests here. }
Następnie test musi znać klasę Application, którą Hilt automatycznie generuje.
Aby umożliwić Hiltowi wstrzykiwanie zależności, musisz utworzyć pustą aktywność o nazwie HiltTestActivity w zestawie źródeł androidTest i oznaczyć ją adnotacją @AndroidEntryPoint. createAndroidComposeRule wykorzystuje tę aktywność jako hosta dla treści, które można łączyć.
Testowanie aplikacji
Testy z instrumentacją, 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 bazowej, zapoznaj się z artykułem Aplikacja niestandardowa na potrzeby testów.
Aplikację testową musisz skonfigurować tak, aby działała w testach z instrumentacją lub testach Robolectric. Poniższe instrukcje nie dotyczą Hilta, ale zawierają ogólne wskazówki dotyczące określania niestandardowej aplikacji do uruchamiania w testach.
Ustawianie aplikacji testowej w testach z instrumentacją
Aby używać aplikacji testowej Hilt w testach z użyciem instrumentacji, musisz skonfigurować nowy program uruchamiający testy. Dzięki temu Hilt będzie działać we wszystkich testach z instrumentacją w Twoim projekcie. Wykonaj te czynności:
- Utwórz klasę niestandardową, która rozszerza
AndroidJUnitRunnerw folderzeandroidTest. - Zastąp funkcję
newApplicationi przekaż nazwę wygenerowanej aplikacji testowej Hilt.
// 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) } }
Następnie skonfiguruj ten program do uruchamiania testów w pliku Gradle zgodnie z opisem w przewodniku po testach jednostkowych z instrumentacją. Upewnij się, że używasz pełnej ścieżki klasy:
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 interfejsu używasz Robolectric, możesz określić, której aplikacji chcesz użyć, w pliku robolectric.properties:
application = dagger.hilt.android.testing.HiltTestApplication
Możesz też skonfigurować aplikację w każdym teście z osobna, używając adnotacji @Config Robolectric:
@HiltAndroidTest @Config(application = HiltTestApplication::class) class SettingsScreenTest { @get:Rule var hiltRule = HiltAndroidRule(this) // Robolectric tests here. }
Funkcje testowania
Gdy Hilt będzie gotowy do użycia w testach, możesz skorzystać z 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ć Hilta, że ma wypełnić pola @Inject, wywołaj hiltRule.inject().
Oto przykład testu z instrumentacją:
@HiltAndroidTest class SettingsScreenTest { @get:Rule(order = 0) val hiltRule = HiltAndroidRule(this) @get:Rule(order = 1) val composeRule = createAndroidComposeRule<HiltTestActivity>() @Inject lateinit var analyticsAdapter: AnalyticsAdapter @Before fun init() { hiltRule.inject() } @Test fun settingsScreen_showsTitle() { composeRule.setContent { SettingsScreen() } composeRule.onNodeWithText("Settings").assertIsDisplayed() // analyticsRepository is available here. } }
Zastępowanie powiązania
Jeśli chcesz wstrzyknąć fałszywą lub testową instancję zależności, musisz poinformować Hilt, aby nie używał powiązania, którego używał w kodzie produkcyjnym, i zamiast tego użył innego. Aby zastąpić powiązanie, musisz zastąpić moduł, który zawiera 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 powiązanie dla
AnalyticsService w ten sposób:
@Module @InstallIn(SingletonComponent::class) abstract class AnalyticsModule { @Singleton @Binds abstract fun bindAnalyticsService( analyticsServiceImpl: AnalyticsServiceImpl ): AnalyticsService }
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 dodaj do niego adnotację @TestInstallIn. Zamiast tego do wszystkich testów w tym folderze wstrzykiwana jest fałszywa zależność.
@Module @TestInstallIn( components = [SingletonComponent::class], replaces = [AnalyticsModule::class] ) abstract class FakeAnalyticsModule { @Singleton @Binds abstract fun bindAnalyticsService( fakeAnalyticsService: FakeAnalyticsService ): AnalyticsService }
Komponenty kompozycyjne zwykle korzystają z tych zależności pośrednio za pomocą elementu ViewModel uzyskanego za pomocą hiltViewModel(), więc wystarczy zastąpić powiązanie w Hilt. Testowany komponent kompozycyjny automatycznie wykrywa fałszywą wartość.
Zastępowanie powiązania w ramach jednego testu
Aby zastąpić powiązanie w jednym teście zamiast we wszystkich, odinstaluj moduł Hilt z testu za pomocą adnotacji @UninstallModules i utwórz w nim nowy moduł testowy.
Zgodnie z AnalyticsServiceprzykładem z poprzedniej wersji zacznij od poinformowania Hilta, aby zignorował moduł produkcyjny, używając w klasie testowej adnotacji @UninstallModules:
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsScreenTest { ... }
Następnie musisz wymienić oprawę. Utwórz w klasie testowej nowy moduł, który definiuje powiązanie testu:
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsScreenTest { @Module @InstallIn(SingletonComponent::class) abstract class TestModule { @Singleton @Binds abstract fun bindAnalyticsService( fakeAnalyticsService: FakeAnalyticsService ): AnalyticsService } // ... }
Zastępuje to tylko powiązanie dla jednej klasy testowej. Jeśli chcesz zastąpić wią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 z użyciem instrumentacji.
Zalecamy używanie @TestInstallIn, kiedy tylko jest to możliwe.
Powiązywanie nowych wartości
Użyj adnotacji @BindValue, aby łatwo powiązać pola w teście z grafem zależności Hilt. Oznacz pole symbolem @BindValue, a zostanie ono powiązane z zadeklarowanym typem pola wraz z wszelkimi kwalifikatorami, które są w nim obecne.
W przykładzie AnalyticsService możesz zastąpić AnalyticsService fałszywą wartością, używając @BindValue:
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsScreenTest { @BindValue @JvmField val analyticsService: AnalyticsService = FakeAnalyticsService() ... }
Upraszcza to zarówno zastępowanie powiązania, jak i odwoływanie się do niego w teście, ponieważ obie te czynności można wykonać jednocześnie.
@BindValue współpracuje 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:
... class SettingsScreenTest { ... @BindValue @ExampleQualifier @Mock lateinit var qualifiedVariable: ExampleCustomType // Robolectric tests here }
Jeśli musisz dodać wielokrotne powiązanie, możesz użyć adnotacji @BindValueIntoSet i @BindValueIntoMap zamiast @BindValue. @BindValueIntoMap wymaga też dodania do pola adnotacji z kluczem mapy.
Przypadki szczególne
Hilt udostępnia też funkcje obsługujące niestandardowe przypadki użycia.
Aplikacja niestandardowa do testów
Jeśli nie możesz użyć adnotacji HiltTestApplication, ponieważ aplikacja testowa musi rozszerzać inną aplikację, dodaj adnotację @CustomTestApplication do nowej klasy lub interfejsu, przekazując wartość klasy bazowej, którą ma rozszerzać wygenerowana aplikacja Hilt.
@CustomTestApplication wygeneruje klasę Application gotową do testowania
za pomocą Hilta, która rozszerza aplikację przekazaną jako parametr.
@CustomTestApplication(BaseApplication::class) interface HiltTestApplication
W tym przykładzie Hilt generuje interfejs Application o nazwie HiltTestApplication_Application, który rozszerza klasę BaseApplication. Ogólnie rzecz biorąc, nazwa wygenerowanej aplikacji to nazwa klasy z adnotacjami z dodanym znakiem _Application. Wygenerowaną aplikację testową Hilt musisz skonfigurować tak, aby działała w testach z użyciem instrumentacji lub testach Robolectric zgodnie z opisem w sekcji Aplikacja testowa.
Wiele obiektów TestRule w teście instrumentowanym
Testy interfejsu Compose łączą już HiltAndroidRule z regułą testu Compose, np. createAndroidComposeRule. Jeśli masz dodatkowe obiekty TestRule, upewnij się, że najpierw uruchomisz HiltAndroidRule. Zadeklaruj kolejność wykonywania za pomocą atrybutu order w przypadku elementu @Rule:
@HiltAndroidTest class SettingsScreenTest { @get:Rule(order = 0) var hiltRule = HiltAndroidRule(this) @get:Rule(order = 1) val composeRule = createAndroidComposeRule<HiltTestActivity>() @get:Rule(order = 2) val otherRule = SomeOtherRule() // UI tests here. }
Możesz też umieścić reguły w tagach RuleChain, a jako regułę zewnętrzną ustawić HiltAndroidRule.
@HiltAndroidTest class SettingsScreenTest { @get:Rule var rule = RuleChain.outerRule(HiltAndroidRule(this)). around(SettingsScreenTestRule(...)) // UI tests here. }
Używanie punktu wejścia przed udostępnieniem komponentu singleton
Adnotacja @EarlyEntryPoint zapewnia możliwość wyjścia z sytuacji, gdy punkt wejścia Hilt musi zostać utworzony, zanim komponent singleton będzie dostępny w teście Hilt.
Więcej informacji o @EarlyEntryPoint znajdziesz w dokumentacji Hilt.
Dodatkowe materiały
Więcej informacji o testowaniu znajdziesz w tych materiałach: