Jedną z zalet korzystania z platform wstrzykiwania zależności, takich jak Hilt, jest możliwość łatwiejszego testowania kodu.
Testy jednostkowe
Wskaźnik nie jest wymagany w przypadku testów jednostkowych, ponieważ podczas testowania klasy korzystającej z wstrzykiwania konstruktora nie trzeba używać Hilt do utworzenia jej wystąpienia. Zamiast tego możesz bezpośrednio wywołać konstruktor klas, przekazując fałszywe lub pozorowane zależności, tak jak w przypadku braku 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 wstrzykuje zależności w taki sam sposób, jak w kodzie produkcyjnym. Testowanie za pomocą narzędzia Hilt nie wymaga obsługi, ponieważ do każdego testu automatycznie generuje nowy zestaw komponentów.
Dodaję zależności testowe
Aby używać Hilt do testowania, uwzględnij w projekcie zależność hilt-android-testing
:
Odlotowe
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
Do każdego testu interfejsu, który używa Hilt z funkcją @HiltAndroidTest
, musisz dodać adnotacje. Odpowiada ona 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 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 znać klasę Application
, którą Hilt generuje automatycznie.
Testuj aplikację
Musisz wykonać testy z instrumentowane korzystające z Hilt w obiekcie Application
, który obsługuje Hilt. Biblioteka udostępnia funkcje HiltTestApplication
do wykorzystania w testach.
Jeśli testy wymagają innej aplikacji podstawowej, zapoznaj się z sekcją Aplikacja niestandardowa do testów.
Musisz skonfigurować aplikację testową tak, aby była uruchamiana w testach instrumentalnych lub testach Robolectric. Poniższe instrukcje nie dotyczą tylko Hilt, ale są to ogólne wskazówki dotyczące wybierania aplikacji niestandardowych do uruchamiania w testach.
Ustawienie aplikacji testowej w testach instrumentowanych
Aby używać aplikacji testowej Hilt w testach instrumentalnych, musisz skonfigurować nowy program uruchamiający test. Dzięki temu Hilt będzie działać we wszystkich testach zinstrumentowanych w 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 mechanizm uruchamiania testów w pliku Gradle zgodnie z opisem w instrumentowanym przewodniku do testów jednostkowych. Pamiętaj, aby użyć pełnej ścieżki klasy:
Odlotowe
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 używasz Robolectric do testowania warstwy interfejsu, możesz w pliku robolectric.properties
określić, której aplikacji chcesz użyć:
application = dagger.hilt.android.testing.HiltTestApplication
Możesz też skonfigurować aplikację w każdym teście oddzielnie, używając adnotacji @Config
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:
Odlotowe
hilt { enableTransformForLocalTests = true }
Kotlin
hilt { enableTransformForLocalTests = true }
Więcej informacji o enableTransformForLocalTests
znajdziesz w dokumentacji Hilt.
Funkcje testowe
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 wstrzykiwać typy do testu, używaj @Inject
do wstrzykiwania 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 wiązania
Jeśli chcesz wstrzyknąć 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ł, który je zawiera, modułem testowym zawierającym powiązania, których chcesz używać w teście.
Na przykład załóżmy, że Twój kod produkcyjny deklaruje powiązanie dla AnalyticsService
w następujący 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
. Wszystkie testy w tym folderze są wstrzykiwane z fałszywą 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.
Korzystając z przykładu AnalyticsService
z poprzedniej wersji, zacznij od polecenia 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 musisz zastąpić powią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 powiązanie tylko dla jednej klasy testowej. Jeśli chcesz zastąpić powiązanie dla wszystkich klas testowych, użyj adnotacji @TestInstallIn
z sekcji powyżej. Powiązanie testowe możesz też umieścić w module test
(w przypadku testów Robolectric) lub w module androidTest
(w przypadku testów zinstruowanych).
Zalecamy, aby w miarę możliwości używać pola @TestInstallIn
.
Powiązanie nowych wartości
Użyj adnotacji @BindValue
, aby łatwo powiązać pola testu z grafem zależności Hilt. Dodaj do pola adnotację @BindValue
, aby będzie ono objęte zadeklarowanym typem pola ze wszystkimi kwalifikatorami, które występują w tym polu.
W przykładzie AnalyticsService
możesz zastąpić element AnalyticsService
fałszywym, używając parametru @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 chcesz dodać wielowiązywanie, zamiast @BindValue
możesz użyć adnotacji @BindValueIntoSet
i @BindValueIntoMap
. @BindValueIntoMap
wymaga dodania adnotacji do pola z adnotacją 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 rozszerzyć inną aplikację, dodać adnotację do nowej klasy lub interfejsu za pomocą @CustomTestApplication
, przekazując wartość klasy bazowej, którą ma rozszerzyć 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
. Ogólnie nazwa wygenerowanej aplikacji to nazwa klasy z adnotacjami z dodanym _Application
. Musisz skonfigurować wygenerowaną aplikację testową Hilt tak, aby była uruchamiana w testach instrumentalnych lub testach Robolectric zgodnie z opisem w sekcji Aplikacja testowa.
Wiele obiektów TestRule w teście instrumentalnym
Jeśli testujesz inne obiekty TestRule
, masz kilka możliwości zapewnienia, że wszystkie reguły będą ze sobą współpracować.
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ż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
. 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żywać właściwości launchFragmentInContainer
z biblioteki androidx.fragment:fragment-testing
za pomocą Hilta, ponieważ opiera się na działaniu, które nie jest oznaczone adnotacją @AndroidEntryPoint
.
Zamiast tego użyj kodu launchFragmentInHiltContainer
z repozytorium architecture-samples
GitHub.
Aby udostępnić komponent singleton, użyj punktu wejścia
Adnotacja @EarlyEntryPoint
zapewnia dostęp awaryjny, gdy trzeba utworzyć punkt wejścia Hilt, zanim komponent singleton będzie dostępny w teście Hilt.
Więcej informacji o @EarlyEntryPoint
znajdziesz w dokumentacji Hilt.