Hilt 測試指南

使用 Hilt 這類依附元件插入架構的其中一項優點,是可更輕鬆地測試程式碼。

單元測試

單元測試並不需要動用 Hilt,因為在測試使用建構函式插入的類別時,您無需透過 Hilt 對該類別進行例項化。您可以改為直接呼叫類別建構函式,只要傳入虛假或模擬的依附元件即可,就像在建構函式未加上註解時的方式一樣:

@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(...)
  }
}

在可組合函式中呼叫 hiltViewModel() 取得的 ViewModel 類別也適用相同規則。在單元測試中,直接使用模擬建構 ViewModel。 如要瞭解狀態如何從 ViewModel 流入可組合函式,請參閱「狀態和 Jetpack Compose」和「狀態提升位置」。

端對端測試

對於整合測試,Hilt 會插入依附元件,就像在正式版程式碼中一樣。使用 Hilt 進行測試無須維護,因為 Hilt 會自動為每項測試產生一組新的元件。

新增測試依附元件

如要在測試中使用 Hilt,請在專案中納入 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")

}

UI 測試設定

您必須用 @HiltAndroidTest 為所有使用 Hilt 的 UI 測試加上註解。這個註解負責為每個測試產生 Hilt 元件。

此外,您需要將 HiltAndroidRule 新增至測試類別。這可管理元件的狀態,並用於在測試中執行插入作業:

@HiltAndroidTest
class SettingsScreenTest {

    @get:Rule(order = 0)
    val hiltRule = HiltAndroidRule(this)

    @get:Rule(order = 1)
    val composeRule = createAndroidComposeRule<HiltTestActivity>()

    // Compose UI tests here.
}

接下來,測試需要知道 Application 類別為何,Hilt 會自動為您產生這個類別。

如要讓 Hilt 插入依附元件,您必須在 androidTest 來源集中建立名為 HiltTestActivity 的空白活動,並加上 @AndroidEntryPoint 註解。createAndroidComposeRule 接著會將這項活動做為可組合內容的主機。

測試應用程式

您必須在支援 Hilt 的 Application 物件中執行使用 Hilt 的檢測設備測試。程式庫提供用於測試的 HiltTestApplication。如果測試需要不同的基礎應用程式,請參閱「用於測試的自訂應用程式」。

您必須將測試應用程式設為在檢測設備測試Robolectric 測試中執行。下列操作說明並非僅適用於 Hilt,而是一般性的指引,可讓您瞭解如何指定要在測試中執行的自訂應用程式。

在檢測設備測試中設定測試應用程式

如要在檢測設備測試中使用 Hilt 測試應用程式,您需要設定新的測試執行器。如此一來,Hilt 就可以用於專案中的所有檢測設備測試。請執行下列步驟:

  1. androidTest 資料夾中建立可擴充 AndroidJUnitRunner 的自訂類別。
  2. 覆寫 newApplication 函式,並傳入產生的 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)
    }
}

接下來,請按照檢測設備單元測試指南所述,在 Gradle 檔案中設定測試執行器。請務必使用完整的類別路徑:

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 註解,在每項測試中個別設定應用程式:

@HiltAndroidTest
@Config(application = HiltTestApplication::class)
class SettingsScreenTest {

  @get:Rule
  var hiltRule = HiltAndroidRule(this)

  // Robolectric tests here.
}

測試功能

當 Hilt 可用於測試之後,您就可以使用多項功能自訂測試程序。

在測試中插入類型

如要在測試中插入類型,請使用 @Inject 插入欄位。如要指示 Hilt 填入 @Inject 欄位,請呼叫 hiltRule.inject()

請參考以下檢測設備測試範例:

@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.
    }
}

取代繫結

如果您需要插入依附元件的虛假或模擬例項,則需指示 Hilt 不要使用其於正式版程式碼中所用的繫結,而要改用不同的繫結。如要取代繫結,您需將包含繫結的模組替換成測試模組,該模組包含您要在測試中使用的繫結。

舉例來說,假設正式版程式碼宣告 AnalyticsService 的繫結,如下所示:

@Module
@InstallIn(SingletonComponent::class)
abstract class AnalyticsModule {

  @Singleton
  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}

如要取代測試中的 AnalyticsService 繫結,請使用虛假依附元件在 testandroidTest 資料夾中建立新的 Hilt 模組,並使用 @TestInstallIn 加上註解。系統會改用虛假依附元件插入該資料夾中的所有測試。

@Module
@TestInstallIn(
    components = [SingletonComponent::class],
    replaces = [AnalyticsModule::class]
)
abstract class FakeAnalyticsModule {

  @Singleton
  @Binds
  abstract fun bindAnalyticsService(
    fakeAnalyticsService: FakeAnalyticsService
  ): AnalyticsService
}

由於可組合函式通常會透過使用 hiltViewModel() 取得的 ViewModel 間接使用這些依附元件,因此只要在 Hilt 中替換繫結即可。受測的可組合函式會自動擷取虛擬函式。

取代單一測試中的繫結

如要取代單一測試 (而非所有測試) 的繫結,請使用 @UninstallModules 註解從測試中解除安裝 Hilt 模組,並在測試中建立新的測試模組。

按照前一個版本的 AnalyticsService 範例,首先請在測試類別中使用 @UninstallModules 註解,讓 Hilt 忽略正式版模組:

@UninstallModules(AnalyticsModule::class)
@HiltAndroidTest
class SettingsScreenTest { ... }

接下來,您必須替換繫結。請在定義測試繫結的測試類別中建立新模組:

@UninstallModules(AnalyticsModule::class)
@HiltAndroidTest
class SettingsScreenTest {

  @Module
  @InstallIn(SingletonComponent::class)
  abstract class TestModule {

    @Singleton
    @Binds
    abstract fun bindAnalyticsService(
      fakeAnalyticsService: FakeAnalyticsService
    ): AnalyticsService
  }

  // ...
}

這只會取代單一測試類別的繫結。如要取代所有測試類別的繫結,請使用上一節的 @TestInstallIn 註解。或者,您也可以將測試繫結放入 test 模組以進行 Robolectric 測試,或放入 androidTest 模組以進行檢測設備測試。建議盡可能使用 @TestInstallIn

繫結新值

使用 @BindValue 註解可輕鬆將測試中的欄位繫結至 Hilt 依附元件圖表。您只要使用 @BindValue 為欄位加上註解,該欄位便會在宣告的欄位類型之下,與該欄位現有的任何限定詞繫結在一起。

AnalyticsService 範例中,您可以使用 @BindValueAnalyticsService 替換為虛假項目:

@UninstallModules(AnalyticsModule::class)
@HiltAndroidTest
class SettingsScreenTest {

  @BindValue @JvmField
  val analyticsService: AnalyticsService = FakeAnalyticsService()

  ...
}

這樣一來,您可以同時在測試中取代及參照繫結,從而簡化這兩項操作。

@BindValue 可與限定詞和其他測試註解搭配使用。舉例來說,如果您使用 Mockito 等測試程式庫,便可在 Robolectric 測試中使用這個註解,如下所示:

...
class SettingsScreenTest {
  ...

  @BindValue @ExampleQualifier @Mock
  lateinit var qualifiedVariable: ExampleCustomType

  // Robolectric tests here
}

如果需要新增多重繫結,您可以使用 @BindValueIntoSet@BindValueIntoMap 註解取代 @BindValue@BindValueIntoMap 會要求您同時使用對應鍵註解為欄位加註。

特殊情況

Hilt 也提供支援非標準用途的功能。

用於測試的自訂應用程式

如果您因測試應用程式需要擴充其他應用程式,而無法使用 HiltTestApplication,請使用 @CustomTestApplication 為新的類別或介面加上註解,並在產生 Hilt 應用程式後,傳入其擴充的基礎類別值。

@CustomTestApplication 會產生 Application 類別,可供您以 Hilt 進行測試,並擴充您以參數傳遞的應用程式。

@CustomTestApplication(BaseApplication::class)
interface HiltTestApplication

在這個範例中,Hilt 會產生名為 HiltTestApplication_ApplicationApplication,可擴充 BaseApplication 類別。一般而言,產生的應用程式名稱會是附加 _Application 的註解類別名稱。您必須將產生的 Hilt 測試應用程式設為在檢測設備測試Robolectric 測試中執行,如「測試應用程式」所述。

檢測設備測試中有多個 TestRule 物件

Compose UI 測試已將 HiltAndroidRule 與 Compose 測試規則 (例如 createAndroidComposeRule) 結合。如有其他 TestRule 物件,請務必先執行 HiltAndroidRule。在 @Rule 上使用 order 屬性宣告執行順序:

@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.
}

或者,您也可以使用 RuleChain 包裝規則,並將 HiltAndroidRule 設為外部規則。

@HiltAndroidTest
class SettingsScreenTest {

  @get:Rule
  var rule = RuleChain.outerRule(HiltAndroidRule(this)).
        around(SettingsScreenTestRule(...))

  // UI tests here.
}

在單例模式元件可用之前使用進入點

如果您在單例模式元件可於 Hilt 測試中使用前就需要建立 Hilt 進入點,不妨採用 @EarlyEntryPoint 註解提供的應急方法。

如要進一步瞭解 @EarlyEntryPoint,請參閱 Hilt 說明文件

其他資源

如要進一步瞭解測試,請參閱下列其他資源:

說明文件

Views content