使用 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.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") }
使用者介面測試設定
您必須將所有透過 @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. }
接著,您的測試需要知道 Hilt 自動為您產生的 Application
類別。
測試應用程式
您必須在支援 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 HiltAndroidRule = HiltAndroidRule(this) @get:Rule(order = 1) var settingsActivityTestRule = SettingsActivityTestRule() // UI tests here. }
Java
@HiltAndroidTest public final class SettingsActivityTest { @Rule(order = 0) public HiltAndroidRule rule = new HiltAndroidRule(this); @Rule(order = 1) public SettingsActivityTestRule rule = new SettingsActivityTestRule(...); // UI tests here. }
launchFragmentInContainer
您不能從 androidx.fragment:fragment-testing
程式庫搭配 launchFragmentInContainer
使用 Hilt,因為其依賴不於 @AndroidEntryPoint
加註的活動。
請改用 architecture-samples
GitHub 存放區中的 launchFragmentInHiltContainer
程式碼。
在單例模式元件可用之前使用進入點
在 Hilt 測試中提供單例模式元件之前,需要先建立 Hilt 進入點時,@EarlyEntryPoint
註解可以提供解決方法。
如要進一步瞭解 @EarlyEntryPoint
,請參閱「Hilt 說明文件」。