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 do testów jednostkowych, ponieważ podczas testowania klasy, która używa wstrzykiwania przez konstruktor, nie musisz używać Hilta do utworzenia instancji tej klasy. Zamiast tego możesz bezpośrednio wywołać konstruktor klasy, przekazując fałszywe lub próbne zależności, tak jakby konstruktor nie był oznaczony 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 utwórz ViewModel bezpośrednio z użyciem obiektów imitujących.
Informacje o tym, jak stan przepływa z ViewModel do funkcji kompozycyjnych, znajdziesz w artykułach State and Jetpack Compose (Stan i Jetpack Compose) oraz Where to hoist state (Gdzie przenosić 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ć Hilt 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, musisz oznaczyć 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 uzyskać informacje o klasie Application, którą Hilt automatycznie generuje.
Aby umożliwić Hilt wstrzykiwanie zależności, musisz utworzyć pustą aktywność o nazwie HiltTestActivity w zbiorze źródeł androidTest i dodać do niej adnotację @AndroidEntryPoint. createAndroidComposeRule używa tej aktywności jako hosta dla treści, które można komponować.
Testowanie aplikacji
Testy z instrumentacją, które używają Hilt, musisz wykonywać 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 skonfigurować aplikację testową tak, aby działała w testach z instrumentacją lub testach Robolectric. Poniższe instrukcje nie dotyczą wyłącznie Hilt, ale są ogólnymi wskazówkami, jak określić niestandardową aplikację do uruchamiania w testach.
Ustawianie aplikacji testowej w testach z instrumentacją
Aby używać aplikacji testowej Hilt w testach z instrumentacją, musisz skonfigurować nowy moduł uruchamiający testy. Dzięki temu Hilt będzie działać we wszystkich testach z instrumentacją w projekcie. Wykonaj te czynności:
- Utwórz klasę niestandardową, która rozszerza klasę
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 moduł uruchamiający testy w pliku Gradle zgodnie z opisem w przewodniku po instrumentowanych testach jednostkowych. 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ć aplikację, której chcesz 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:
@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 poinstruować Hilt, żeby 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 próbną instancję zależności, musisz poinformować Hilt, aby nie używał powiązania, którego używał w kodzie produkcyjnym, tylko innego. Aby zastąpić powiązanie, musisz zastąpić moduł, który je zawiera, modułem testowym zawierającym powiązania, których chcesz użyć w teście.
Załóżmy na przykład, że kod produkcyjny deklaruje wią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ć wią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 wszystkie testy w tym folderze są wstrzykiwane z fałszywą zależnością.
@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 przez obiekt ViewModel uzyskany za pomocą hiltViewModel(), dlatego wystarczy zastąpić powiązanie w Hilt. Testowana funkcja kompozycyjna automatycznie pobiera fałszywy obiekt.
Zastępowanie powiązania w jednym teście
Aby zastąpić powiązanie w jednym teście zamiast we wszystkich, odinstaluj moduł Hilt z testu za pomocą adnotacji @UninstallModules i utwórz nowy moduł testowy wewnątrz testu.
Zgodnie z przykładem AnalyticsService z poprzedniej wersji zacznij od poinstruowania Hilt, aby zignorował moduł produkcyjny, używając adnotacji @UninstallModules w klasie testowej:
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsScreenTest { ... }
Następnie musisz zastąpić powiązanie. Utwórz nowy moduł w klasie testowej, który definiuje powiązanie testowe:
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsScreenTest { @Module @InstallIn(SingletonComponent::class) abstract class TestModule { @Singleton @Binds abstract fun bindAnalyticsService( fakeAnalyticsService: FakeAnalyticsService ): AnalyticsService } // ... }
Spowoduje to zastąpienie powiązania tylko w przypadku pojedynczej 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 instrumentacją.
Zalecamy, aby o ile to możliwe, używać @TestInstallIn.
Wiązanie nowych wartości
Użyj adnotacji @BindValue, aby łatwo powiązać pola w teście z wykresem zależności Hilt. Dodaj do pola adnotację @BindValue, a zostanie ono powiązane z zadeklarowanym typem pola z dowolnymi kwalifikatorami, które są obecne w tym polu.
W przykładzie AnalyticsService możesz zastąpić AnalyticsService fałszywym obiektem, 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ż umożliwia wykonanie obu tych czynności 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 ich użyć w teście Robolectric w ten sposób:
... class SettingsScreenTest { ... @BindValue @ExampleQualifier @Mock lateinit var qualifiedVariable: ExampleCustomType // Robolectric tests here }
Jeśli chcesz dodać multibinding, możesz użyć adnotacji @BindValueIntoSet i @BindValueIntoMap zamiast @BindValue. @BindValueIntoMap wymaga również 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ć HiltTestApplication, ponieważ aplikacja testowa musi rozszerzać inną aplikację, dodaj adnotację do nowej klasy lub interfejsu za pomocą @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.
@CustomTestApplication(BaseApplication::class) interface HiltTestApplication
W tym przykładzie Hilt generuje obiekt Application o nazwie HiltTestApplication_Application, który rozszerza klasę BaseApplication. Ogólnie nazwa wygenerowanej aplikacji to nazwa klasy z adnotacją z dodanym ciągiem _Application. Musisz skonfigurować wygenerowaną aplikację testową Hilt tak, aby działała w testach z instrumentacją lub testach Robolectric, zgodnie z opisem w sekcji Aplikacja testowa.
Wiele obiektów TestRule w teście z instrumentacją
Testy interfejsu Compose łączą już HiltAndroidRule z regułą testową Compose, taką jak createAndroidComposeRule. Jeśli masz dodatkowe obiekty TestRule, upewnij się, że HiltAndroidRule uruchamia się jako pierwszy. Zadeklaruj kolejność wykonywania za pomocą atrybutu order w @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ż opakować reguły w RuleChain, umieszczając HiltAndroidRule jako regułę zewnętrzną.
@HiltAndroidTest class SettingsScreenTest { @get:Rule var rule = RuleChain.outerRule(HiltAndroidRule(this)). around(SettingsScreenTestRule(...)) // UI tests here. }
Używanie punktu wejścia, zanim komponent singleton będzie dostępny
Adnotacja @EarlyEntryPoint zapewnia wyjście awaryjne, 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: