

Dagger之十八、在测试中使用 Hilt
source link: http://blog.chengyunfeng.com/?p=1130
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在测试中也可以使用 Hilt 来注入对象,配合使用 Hilt 测试将更加容易。通过 Hilt ,测试代码可以获取到 Dagger 所管理的依赖对象、还可以为测试提供专门的依赖对象。每个测试都有自己的 Hilt 部件,所以可以很容易的为每个测试提供不一样的依赖对象。
当前 Hilt 还处于 Alpha 预览版本,只支持 Android instrumentation 测试 和 Robolectric 测试,在 Android Studio 中运行测试还需要一点额外的配置。
在 app/build.gradle
中添加 Hilt 测试库
如果使用 Kotlin 的话,需要用 kapt 来替代 annotationProcessor:
默认情况下,Hilt gradle 插件会修改 instrumented
测试类的字节码(位于 androidTest
文件目录中的代码),但是对于本地单元测试代码需要一点额外的配置 Hilt 才能修改这些类(位于 test
文件目录中的代码)。
在项目 Gradle module 的 build.gradle
中,通过如下配置可以在单元测试中启用对 @AndroidEntryPoint
类的字节码修改:
只有当从命令行(通过
./gradlew test
启动 Gradle)运行的时候,上面配置的enableTransformForLocalTests
参数才生效。在 Android Studio 中通过测试类或者函数旁边的运行图标(绿色三角图标)来运行单元测试并不会生效。所以对于从 Android Studio 来直接运行测试,还需要如下额外的配置。
在 Android Studio 中运行测试
由于在 Android Studio 中运行测试的时候会忽略 Hilt 动态修改过的测试类。安卓开发团队正在研究如何修复这个问题,在该问题修复之前呢,有如下几种方式来避免这个问题。
第一种方法就是不要使用 Android Studio 来执行 Robolectric 测试,通过 Gradle 命令行来运行这些测试(比如 ./gradlew test 或者 ./gradlew testDebug)。
第二种方法是在 Android Studio 中创建一个自定义的测试配置,通过该配置让 Android Studio 通过 Gradle 来执行测试任务。
通过 Android Studio 菜单 Run
-> Edit Configurations...
打开 Run/Debug Configuration
配置对话框,在该对话框中点击 +
号添加一个 Gradle 类型的配置,在该配置中名字可以随便设置,下面的配置中 Gradle project 为要执行测试的代码所在的 Gradle module; Task 为要执行的测试任务,通常为 test 或者 testDebug;Arguments 为需要执行的测试类,比如 --tests MyTestClassSee
。
https://dagger.dev/hilt/robolectric-test-configuration.jpg
在测试中使用 Hilt
经过上面的配置后,就可以开始在测试中使用 Hilt 了。要使用 Hilt 通常需要如下三个步骤:
- 在测试类上使用
@HiltAndroidTest
- 添加
HiltAndroidRule
作为测试的 Rule - 使用
HiltTestApplication
作为测试的Android Application
类
在 Robolectric 或者 instrumentation 测试中需要使用到
HiltTestApplication
,在后面会详细介绍如何使用。
如果你的测试需要使用自定义的 Application
类,请参考后面的 自定义测试Application一小节的内容。
如果你的测试需要使用多个测试 Rules,请参考后面的 Hilt rule顺序一小节。
使用依赖对象
一个测试通常需要从 Hilt 部件中获取依赖对象。下面分别介绍如何从不同的安卓组件中来获取需要的依赖对象。
获取 ApplicationComponent 中的对象
ApplicationComponent
中的依赖对象可以通过 @Inject
注解来实现注入。注意:只有当调用了 HiltAndroidRule#inject()
函数以后,这些需要注入的变量才被赋值。
获取 ActivityComponent 中的对象
需要一个 Hilt Activity
实例才能从 ActivityComponent
中获取依赖对象。可以通过如下两种方式来实现这个操作。
第一种就是在测试类中定义个 Activity
内部类,并且在这个内部类中使用 @Inject
来定义需要注入的变量,然后通过这个内部类的对象来获取需要注入的依赖对象。
当你的测试类中已经有 Hilt Activity
实例了,则可以通过定义一个 EntryPoint
来从 ActivityComponent
中获取里面的依赖对象。
获取 FragmentComponent 中的对象
获取 FragmentComponent
中的依赖对象和上面的 Activity
操作方式类似。不同的地方在于,从 FragmentComponent
部件中获取依赖对象不仅仅需要 Fragment
实例还需要该 Fragemnt 所在的 Activity
实例:
注意:由于当前没有办法来指定 Activity 类,所以 Hilt 还不支持
FragmentScenario
,目前解决该问题的方式是启动一个 Hilt Activity,然后把 Fragment 添加到该 Activity 中。
添加测试依赖对象
在测试的时候,可能需要使用一些测试版本的依赖对象,这些对象只在测试中使用,在应用发布的时候并不包含这些对象;另外对于同一个类型的对象,可能测试需要的实例和应用发布中需要的实例也是有区别的。本节内容来介绍如何为测试环境提供不同的依赖对象。
内部 Dagger 模块
通常情况下,使用 @InstallIn
注解的模块在每个测试上都安装到了一个 Hilt 部件中。如果想把一些依赖对象只安装到部分测试上,可以在这个测试中使用内部 Dagger 模块
这样当两个不同的测试类都需要一个不同的 Bar
对象的时候,可以在每个测试类中定义自己的内部 Dagger 模块,他们两个互不干扰。
上面定义的是 static 类型的内部类模块,在静态模块中无法访问外部类的对象,如果你需要访问外部类中的对象,则可以删除 static
修饰符,把 模块 内部类定义为非静态的, 这样模块里面的 @Providers
函数可以获取外部类中的对象作为参数。
注意:在 Hilt 中 @InstallIn 模块的构造函数不能有参数。
@BindValue
对于上面这种简单的提供依赖对象的方式,可以通过 @BindValue
注解来更简单的实现上面的功能。
比如上面的 FakeBarModule
模块中的 @Providers
函数的实现很简单,只是通过 FakeBar
的构造函数来创建一个对象返回。那么使用 @BindValue
就可以更容易的实现该功能:
@BindValue
的意思是,当你需要注入一个 Bar
对象的时候, 直接用 fakeBar
这个变量的值即可。
由于 @BindValue
的变量定义到了测试类中并被该测试类控制,所以在 @BindValue
上是不支持范围修饰符的。 如果你想确保这个变量对象是单例的,则可以只在变量初始化的时候设置这个变量的值,其他地方不要修改这个变量的值即可。 如果该变量的状态是可变的,则可以在 @Before
函数中对该变量设置初始值,这样每个测试执行的时候该变量的值都是一样的。
同样对于 Dagger 中的绑定到集合功能 Hilt 也提供了支持。这些注解分别是 @BindValueIntoSet
、@BindElementsIntoSet
、@BindValueIntoMap
。
注意事项
如果使用了 ActivityScenarioRule
,则需要特别注意 @BindValue
和 非静态的内部模块类的使用。 ActivityScenarioRule
在调用 @Before
函数之前就创建好了 Activity 对象,所以当 @BindValue
变量在 @Before
函数里面初始化的时候,可能导致 Activity 中被注入的变量还没有被初始化。 所以对于这种情况,请在定义 @BindValue
变量的地方直接初始化该变量的值。
替换依赖对象
在测试的时候,把正式环境中的对象替换为测试环境中的对象是一个常见的需求,例如大量使用的 mock 对象。在 Hilt 测试中可以使用 @UninstallModules
注解来把正式环境中定义的模块给卸载了,然后再安装一个测试环境下的模块:
比如上面的 ProdFooModule
模块是用于正式环境的,在该模块中提供了依赖对象 Foo
类,通过 @UninstallModules(ProdFooModule.class)
把该模块从测试环境中给卸载了,然后通过 @BindValue Foo fakeFoo = new FakeFoo();
从新定义了依赖对象 Foo
。
如果在 ProdFooModule
模块中提供了两个对象 Foo
和 Bar
,但是在测试的时候你只需要替代 Foo
,那么需要把 Foo
和 Bar
分别定义到两个不同的模块中去,然后在测试环境中把包含 Foo
的模块给卸载了。
自定义测试Application
在测试的时候,每个测试都需要一个测试 Application, Hilt 定义了一个继承自 MultiDexApplication
的默认 HiltTestApplication
。如果你需要使用自定义的 Application 的话,则需要使用 @CustomTestApplication
注解。
如果你的测试需要继承自一个特殊的 Application 类,则使用 @CustomTestApplication
可以生成一个继承自这个特殊类的 测试 Application。
在一个接口或者类中使用 @CustomTestApplication
,并指定需要继承的特殊 Application 类即可。
在上面的示例中, Hilt 生成一个名字为 MyCustom_Application
的类,该类会继承自 MyBaseApplication
这个类。
在测试中不推荐使用 @CustomTestApplication
,最好使用默认的 HiltTestApplication
,这样将来更容易的组织和重用代码。如果你确实要使用自定义测试Application,那么有如下两点需要注意:
- 在 instrumentation 测试中,每个测试和每个测试中的测试函数都使用的是同一个 Application 实例,所以不要在自定义Application 类中保存应用的状态,避免不小心把一个测试中的状态给泄露到另外一个测试中去。
- 在测试Application 中,Hilt 部件并不是在
super#onCreate
函数中被创建的。这种原因是:@BindValue
这种特殊的功能需要访问测试对象实例引起的,只有当Application#onCreate
函数创建后,这些测试对象实例才存在。所以和正式环境中不同,在测试环境中自定义的测试Application 不能在Application#onCreate
函数中调用 Hilt 部件。 比如在正式环境中,在Application#onCreate
中可以注入 Application 中需要注入的对象,但是在自定义测试Application中则不能这样操作。
Hilt rule 顺序
如果你的测试中使用了多个测试 Rule,确保 HiltAndroidRule
最先执行。比如 ActivityScenarioRule
调用 Activity#onCreate
函数,对于 Hilt Activity 需要在该函数中访问 Hilt 部件来执行注入操作。所以 ActivityScenarioRule
应该在 HiltAndroidRule
执行后再执行,这样确保 Activity 中的注入变量被正确赋值了。
如果你使用的 JUnit 版本小于 4.13,则可以使用 RuleChain 来指定 Rule 的顺序。
Robolectric 测试
对于 Robolectric
测试所需要的 Application 可以通过 @Config
来配置也可以通过全局的 robolectric.properties
来配置。 对于 Hilt 测试,该 Application 必须是 HiltTestApplication
或者是 自定义的测试 Application。
当你的测试需要同时在 Robolectric
和 Android instrumentation
环境中运行的时候,由于在 Android instrumentation
测试环境中无法访问 @Config
注解,所以只能使用 robolectric.properties
配置文件的方式来定义。
Instrumentation 测试
对于 Android instrumentation
测试,Application 对象可以通过自定义的 test runner 来提供。通过继承 AndroidJUnitRunner
类并重写 newApplication
函数:
然后把上面的自定义 Test runner 在 build.gradle
配置文件中配置一下:
上面就是如何在测试环境中使用 Hilt 的介绍,最后一节课将通过一个示例项目一步一步的把项目迁移到 hilt 以及如何测试。
Recommend
-
21
Migrate from Dagger to Hilt — A Step by Step GuidePhoto by Emile Perron on
-
12
#Featured #Latest #HiltHilt and Dagger under the hood - MAD Skills6,334 viewsSep 7, 202...
-
10
Dagger之十九、Hilt 迁移实战 作者: rain 分类: Android Training 发布时间: 2021-10-...
-
6
从 Dagger 迁移到 Hilt 的难易程度取决于你应用的复杂度以及是如何使用 Dagger 部件的。本节课介绍迁移到 Hilt 的常规步骤,以及迁移过程中会遇到的一些常见问题。 从 Dagger 迁移到 Hilt 一般有下面四个步骤: 提前规划下大概要迁移的内容
-
9
Dagger之十四、Hilt 对 Jetpack 库的支持 作者: rain 分类: Android Training 发布时...
-
9
Dagger之十五、Hilt 中的单态部件 作者: rain 分类: Android Training 发布时间: 2021...
-
7
Dagger之十三、好刀配好鞘 — Hilt 作者: rain 分类: Android Training 发布时间: 2021...
-
5
dagger and Hilt 从入门到入门 ...
-
19
Android Dagger/Hilt vs Koin for Jetpack Compose Apps Ahh, here we go again. The eternal struggle between Dagger/Hilt and Koin. Let's see which one is better for Jetpack Comp...
-
8
From Dagger & Hilt into the multiplatform world with kotlin-injectEmbracing compile-time safety across platforms
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK