使用 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 适用于您项目中的所有仪器测试。请执行以下步骤:
- 在
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
注解。或者,您可以将测试绑定放在 Robolectric 测试的 test
模块中,或将测试绑定放在仪器测试的 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
将生成一个可以使用 Hilt 进行测试的 Application
类,它扩展了您作为参数传入的应用程序。
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
相同的级别上,只要 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 文档。