Jedną z zalet korzystania z platform wstrzykiwania zależności, takich jak Hilt, jest łatwiejsze testowanie kodu.
Testy jednostkowe
Hilt nie jest potrzebny w przypadku testów jednostkowych, ponieważ podczas testowania klasy, która korzysta z wstrzykiwania przez konstruktor, 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 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 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
:
Groovy
dependencies { // For Robolectric tests. testImplementation 'com.google.dagger:hilt-android-testing:2.56.2' // ...with Kotlin. kaptTest 'com.google.dagger:hilt-android-compiler:2.56.2' // ...with Java. testAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.56.2' // For instrumented tests. androidTestImplementation 'com.google.dagger:hilt-android-testing:2.56.2' // ...with Kotlin. kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.56.2' // ...with Java. androidTestAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.56.2' }
Kotlin
dependencies { // For Robolectric tests. testImplementation("com.google.dagger:hilt-android-testing:2.56.2") // ...with Kotlin. kaptTest("com.google.dagger:hilt-android-compiler:2.56.2") // ...with Java. testAnnotationProcessor("com.google.dagger:hilt-android-compiler:2.56.2") // For instrumented tests. androidTestImplementation("com.google.dagger:hilt-android-testing:2.56.2") // ...with Kotlin. kaptAndroidTest("com.google.dagger:hilt-android-compiler:2.56.2") // ...with Java. androidTestAnnotationProcessor("com.google.dagger:hilt-android-compiler:2.56.2") }
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:
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.
Testowanie aplikacji
Testy z instrumentacją, które korzystają z 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 bazowej, zapoznaj się z artykułem Aplikacja niestandardowa na potrzeby testów.
Aplikację testową musisz skonfigurować tak, aby działała w ramach testów z użyciem instrumentacji lub testów Robolectric. Poniższe instrukcje nie są specyficzne dla 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 instrumentacją, musisz skonfigurować nowy program 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
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 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:
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" } }
Ustawianie aplikacji testowej w testach Robolectric
Jeśli do testowania warstwy interfejsu używasz Robolectric, możesz określić, której aplikacji używać 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:
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 lokalnych testach jednostkowych, stosując w pliku build.gradle
modułu tę konfigurację:
Groovy
hilt { enableTransformForLocalTests = true }
Kotlin
hilt { enableTransformForLocalTests = true }
Więcej informacji o enableTransformForLocalTests
znajdziesz w dokumentacji Hilt.
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 pól. Aby poinformować Hilta, że ma wypełnić pola @Inject
, wywołaj hiltRule.inject()
.
Oto przykład testu z instrumentacją:
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 musisz 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:
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ścią i dodaj do niego adnotację @TestInstallIn
. Zamiast tego do wszystkich testów w tym folderze wstrzykiwana jest 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 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 w nim nowy moduł testowy.
Zgodnie z AnalyticsService
przykładem z poprzedniej wersji zacznij od poinformowania Hilta, aby zignorował moduł produkcyjny, używając w klasie testowej adnotacji @UninstallModules
:
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 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 ); } ... }
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ć testowe powiązanie 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 za pomocą symbolu @BindValue
, a zostanie ono powiązane z zadeklarowanym typem pola z wszelkimi kwalifikatorami, które są w nim obecne.
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ę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 ich użyć 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 chcesz 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ć 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ą Hilta, 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 klasę Application
o nazwie HiltTestApplication_Application
, która 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
Jeśli w teście masz inne TestRule
, istnieje kilka sposobów, aby upewnić się, że wszystkie reguły działają razem.
Możesz połączyć 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żywać obu reguł na tym samym poziomie, o ile najpierw zostanie wykonana reguła HiltAndroidRule
. Określ kolejność wykonywania za pomocą 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ć launchFragmentInContainer
z biblioteki androidx.fragment:fragment-testing
z Hiltem, ponieważ zależy ona od aktywności, która nie jest oznaczona adnotacją @AndroidEntryPoint
.
Zamiast tego użyj kodu z repozytorium launchFragmentInHiltContainer
architecture-samples
GitHub.
Używanie punktu wejścia przed udostępnieniem komponentu singleton
Adnotacja @EarlyEntryPoint
zapewnia możliwość wyjścia z sytuacji, 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.