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

  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 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 說明文件」。