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

接下来,您的测试需要了解 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 注解。或者,您可以将测试绑定放在 Robolectric 测试的 test 模块中,或将测试绑定放在仪器测试的 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 将生成一个可以使用 Hilt 进行测试的 Application 类,它扩展了您作为参数传入的应用程序。

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 相同的级别上,只要 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 进行注解的 Activity。

请改用 launchFragmentInHiltContainer 代码(来自 architecture-samples GitHub 存储库)。

在单例组件可用之前使用入口点

@EarlyEntryPoint 注解提供了一种解决方案,当 Hilt 入口点需要在 Hilt 测试中单例组件可用之前创建时,可以使用该注解。

有关 @EarlyEntryPoint 的更多信息,请参阅 Hilt 文档