Hilt 測試指南

使用 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 就可以用於專案中的所有檢測設備測試。請執行下列步驟:

  1. androidTest 資料夾中建立可擴充 AndroidJUnitRunner 的自訂類別。
  2. 覆寫 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 繫結,請使用虛假依附元件在 testandroidTest 資料夾中建立新的 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 範例中,您可以使用 @BindValueAnalyticsService 替換為虛假項目:

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_ApplicationApplication,可擴充 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 說明文件