使用 Hilt 這類依附元件插入架構的其中一項優點,是可更輕鬆地測試程式碼。
單元測試
單元測試並不需要動用 Hilt,因為在測試使用建構函式插入的類別時,您無需透過 Hilt 對該類別進行例項化。您可以改為直接呼叫類別建構函式,只要傳入虛假或模擬的依附元件即可,就像在建構函式未加上註解時的方式一樣:
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(...); } }
端對端測試
對於整合測試,Hilt 會插入依附元件,就像在正式版程式碼中一樣。使用 Hilt 進行測試無須維護,因為 Hilt 會自動為每項測試產生一組新的元件。
新增測試依附元件
如要在測試中使用 Hilt,請在專案中納入 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") }
UI 測試設定
您必須用 @HiltAndroidTest
為所有使用 Hilt 的 UI 測試加上註解。這個註解負責為每個測試產生 Hilt 元件。
此外,您需要將 HiltAndroidRule
新增至測試類別。這可管理元件的狀態,並用於在測試中執行插入作業:
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. }
接下來,測試需要知道 Application
類別為何,Hilt 會自動為您產生這個類別。
測試應用程式
您必須在支援 Hilt 的 Application
物件中執行使用 Hilt 的檢測設備測試。程式庫提供用於測試的 HiltTestApplication
。如果測試需要不同的基礎應用程式,請參閱「用於測試的自訂應用程式」。
您必須將測試應用程式設為在檢測設備測試或 Robolectric 測試中執行。下列操作說明並非僅適用於 Hilt,而是一般性的指引,可讓您瞭解如何指定要在測試中執行的自訂應用程式。
在檢測設備測試中設定測試應用程式
如要在檢測設備測試中使用 Hilt 測試應用程式,您需要設定新的測試執行器。如此一來,Hilt 就可以用於專案中的所有檢測設備測試。請執行下列步驟:
- 在
androidTest
資料夾中建立可擴充AndroidJUnitRunner
的自訂類別。 - 覆寫
newApplication
函式,並傳入產生的 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); } }
接下來,請按照檢測設備單元測試指南所述,在 Gradle 檔案中設定測試執行器。請務必使用完整的類別路徑:
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" } }
在 Robolectric 測試中設定測試應用程式
如果您透過 Robolectric 測試 UI 層,可以在 robolectric.properties
檔案中指定要使用的應用程式:
application = dagger.hilt.android.testing.HiltTestApplication
或者,您也可以使用 Robolectric 的 @Config
註解,在每項測試中個別設定應用程式:
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. }
如果您使用 Android Gradle 外掛程式 4.2 以下版本,請在模組的 build.gradle
檔案中套用下列設定,以在本機單元測試中啟用轉換 @AndroidEntryPoint
類別的功能:
Groovy
hilt { enableTransformForLocalTests = true }
Kotlin
hilt { enableTransformForLocalTests = true }
如要進一步瞭解 enableTransformForLocalTests
,請參閱 Hilt 說明文件。
測試功能
當 Hilt 可用於測試之後,您就可以使用多項功能自訂測試程序。
在測試中插入類型
如要在測試中插入類型,請使用 @Inject
插入欄位。如要指示 Hilt 填入 @Inject
欄位,請呼叫 hiltRule.inject()
。
請參考以下檢測設備測試範例:
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. } }
取代繫結
如果您需要插入依附元件的虛假或模擬例項,則需指示 Hilt 不要使用其於正式版程式碼中所用的繫結,而要改用不同的繫結。如要取代繫結,您需將包含繫結的模組替換成測試模組,該模組包含您要在測試中使用的繫結。
舉例來說,假設正式版程式碼宣告 AnalyticsService
的繫結,如下所示:
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 ); }
如要取代測試中的 AnalyticsService
繫結,請使用虛假依附元件在 test
或 androidTest
資料夾中建立新的 Hilt 模組,並使用 @TestInstallIn
加上註解。系統會改用虛假依附元件插入該資料夾中的所有測試。
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 ); }
取代單一測試中的繫結
如要取代單一測試 (而非所有測試) 的繫結,請使用 @UninstallModules
註解從測試中解除安裝 Hilt 模組,並在測試中建立新的測試模組。
按照前一個版本的 AnalyticsService
範例,首先請在測試類別中使用 @UninstallModules
註解,讓 Hilt 忽略正式版模組:
Kotlin
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsActivityTest { ... }
Java
@UninstallModules(AnalyticsModule.class) @HiltAndroidTest public final class SettingsActivityTest { ... }
接下來,您必須替換繫結。請在定義測試繫結的測試類別中建立新模組:
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 ); } ... }
這只會取代單一測試類別的繫結。如要取代所有測試類別的繫結,請使用上一節的 @TestInstallIn
註解。或者,您也可以將測試繫結放入 test
模組以進行 Robolectric 測試,或放入 androidTest
模組以進行檢測設備測試。建議盡可能使用 @TestInstallIn
。
繫結新值
使用 @BindValue
註解可輕鬆將測試中的欄位繫結至 Hilt 依附元件圖表。您只要使用 @BindValue
為欄位加上註解,該欄位便會在宣告的欄位類型之下,與該欄位現有的任何限定詞繫結在一起。
在 AnalyticsService
範例中,您可以使用 @BindValue
將 AnalyticsService
替換為虛假項目:
Kotlin
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsActivityTest { @BindValue @JvmField val analyticsService: AnalyticsService = FakeAnalyticsService() ... }
Java
@UninstallModules(AnalyticsModule.class) @HiltAndroidTest class SettingsActivityTest { @BindValue AnalyticsService analyticsService = FakeAnalyticsService(); ... }
這樣一來,您可以同時在測試中取代及參照繫結,從而簡化這兩項操作。
@BindValue
可與限定詞和其他測試註解搭配使用。舉例來說,如果您使用 Mockito 等測試程式庫,便可在 Robolectric 測試中使用這個註解,如下所示:
Kotlin
... class SettingsActivityTest { ... @BindValue @ExampleQualifier @Mock lateinit var qualifiedVariable: ExampleCustomType // Robolectric tests here }
Java
... class SettingsActivityTest { ... @BindValue @ExampleQualifier @Mock ExampleCustomType qualifiedVariable; // Robolectric tests here }
如果需要新增多重繫結,您可以使用 @BindValueIntoSet
和 @BindValueIntoMap
註解取代 @BindValue
。@BindValueIntoMap
會要求您同時使用對應鍵註解為欄位加註。
特殊情況
Hilt 也提供支援非標準用途的功能。
用於測試的自訂應用程式
如果您因測試應用程式需要擴充其他應用程式,而無法使用 HiltTestApplication
,請使用 @CustomTestApplication
為新的類別或介面加上註解,並在產生 Hilt 應用程式後,傳入其擴充的基礎類別值。
@CustomTestApplication
會產生 Application
類別,可供您以 Hilt 進行測試,並擴充您以參數傳遞的應用程式。
Kotlin
@CustomTestApplication(BaseApplication::class) interface HiltTestApplication
Java
@CustomTestApplication(BaseApplication.class) interface HiltTestApplication { }
在這個範例中,Hilt 會產生名為 HiltTestApplication_Application
的 Application
,可擴充 BaseApplication
類別。一般而言,產生的應用程式名稱會是附加 _Application
的註解類別名稱。您必須將產生的 Hilt 測試應用程式設為在檢測設備測試或 Robolectric 測試中執行,如「測試應用程式」所述。
檢測設備測試中有多個 TestRule 物件
如果測試中有其他 TestRule
物件,可以透過多種方式確保所有規則都能搭配運作。
您可以按照以下方式一併納入規則:
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. }
或者,您也可以在同一層級使用這兩項規則,只要 HiltAndroidRule
先執行就沒問題。請使用 @Rule
註解中的 order
屬性指定執行順序。這個方法僅適用於 JUnit 4.13 以上版本:
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
您無法將 androidx.fragment:fragment-testing
程式庫中的 launchFragmentInContainer
搭配 Hilt 使用,因為其依附的活動不帶 @AndroidEntryPoint
註解。
請改用 architecture-samples
GitHub 存放區中的 launchFragmentInHiltContainer
程式碼。
在單例模式元件可用之前使用進入點
如果您在單例模式元件可於 Hilt 測試中使用前就需要建立 Hilt 進入點,不妨採用 @EarlyEntryPoint
註解提供的應急方法。
如要進一步瞭解 @EarlyEntryPoint
,請參閱 Hilt 說明文件。