嗨,我有一个类,它实现了一个抽象,参数类型为Foo,并通过EntryPointAccessors直接提供给抽象类。像这样
SomethingImportantBuilderImpl(
...
): AbstractSomethingImportantBuilder(
foo: Foo = EntryPointAccessors.fromApplication(applicationContext, FooEntryPoint::class.java).provideFoo()
)
一切都按预期工作,但现在我如何通过EntryPointAccessor提供Foo在单元测试中的SomethingImportantBuilder ?
每次运行test时,它都会说下一个:
Could not find an Application in the given context: null
java.lang.IllegalStateException: Could not find an Application in the given context: null
at dagger.hilt.android.internal.Contexts.getApplication(Contexts.java:42)
at dagger.hilt.android.EntryPointAccessors.fromApplication(EntryPointAccessors.java:37)
任何帮助/建议吗?我如何在单元测试中提供这种依赖?
编辑1:如果我试着像这样嘲笑它:every { EntryPointAccessors.fromApplication(any(), FooEntryPoint::class.java).provideFoo() } returns mockk(relaxed = true)
抛出第二个错误:android.content.Context.getApplicationContext()Landroid/content/Context;
编辑2:试图使用mock,所以我模拟了FooEntryPoint接口,其中hilt在Singleton/Context接口中实现了这个入口点。. 不工作,因为它没有初始化上下文:
private lateinit var context: Application
@Before
fun setup(){
context = mockk(moreInterfaces = arrayOf(FooEntryPoint::class, GeneratedComponent::class), relaxed = true)
}
@Test
fun useContext(){
println(context) //using context throws the error saying that lateinit var is not initialized.
}
lateinit property context has not been initialized kotlin.UninitializedPropertyAccessException: lateinit property context has not been initialized
错误
还是同样的问题。我如何为EntryPoint提供applicationContext ?
你面临着两个不同的问题:
- 在UnitTest中使用刀柄 需要在UnitTest中注入applicationContext
ad 1)我90%肯定你不能在UnitTests中使用Hilt,但如果有人提供不同的信息,我会很高兴地更新这个答案。为什么在UnitTests中不能使用Hilt ?原因与第二条相同(见下文),因为Hilt与AndroidFramework紧密耦合。
ad 2)这是绝对不可能的,这实际上是Android上UnitTests和instrumentation测试之间的主要区别:后者是在Android环境中运行的(真实设备,模拟器等)。正因为如此,你可以测试使用Android类的代码组件。最著名的是ApplicationContext
,Context
,Activity
和Fragment
。另一方面,unittest是原始的逻辑测试,与任何Android框架组件无关。UnitTest不需要Android类。没有Android类可以用于unittest。
在你的例子中,你想要在一个需要注入ApplicationContext的测试中使用Hilt。这两个都是强有力的指示,你必须在仪器测试中这样做。这里有一个分步说明如何为本视频中描述的仪表测试设置刀柄:
https://www.youtube.com/watch?v=nOp_CEP_EjM
- 添加如下gradle dependencies:
androidTestImplementation "com.google.dagger:hilt-android-testing:2.38.1"
kaptAndroidTest "com.google.dagger:hilt-android-comiler:2.38.1"
- 要使用手柄,您需要用一个小型自定义Runner替换
AndroidJUnitRunner
。为此,创建HiltTestRunner
:
import android.app.Application
import android.content.Context
import androidx.test.runner.AndroidJUnitRunner
import dagger.hilt.android.testing.HiltTestApplication
class HiltTestRunner : AndroidJUnitRunner() {
override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application {
return super.newApplication(cl, HiltTestApplication::class.java.name, context)
}
}
- 要告诉gradle关于新的测试运行器,打开
/app/build.gradle
并交换以下行:
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
使用您刚刚创建的HiltTestRunner
的类引用:
testInstrumentationRunner "your.package.HiltTestRunner"
基本上就是这样。现在你可以在你的测试文件夹中创建一个新的Module,就像你在普通的app文件夹中所做的那样。例如:
@Module
@InstallIn(SingletonComponent::class)
class TestDatabaseModule {
@Provides
@Named("test_database")
fun provideDatabase(@ApplicationContext applicationContext: Context): MyDatabase {
return Room.inMemoryDatabaseBuilder(applicationContext, MyDatabase::class.java)
.allowMainThreadQueries()
.build()
}
}
要在TestCode中注入这个数据库,您需要创建一个HiltAndroidRule
,并在@Before
方法中调用hiltRule.inject()
:
@HiltAndroidTest
class UserDaoTest {
@get:Rule
var hiltRule = HiltAndroidRule(this)
@get:Rule
val testCoroutineRule = TestCoroutineRule()
@Inject
lateinit var database: MyDatabase
private lateinit var sut: UserDao
@Before
fun createDb() {
hiltRule.inject()
sut = database.userDao()
}
@After
@Throws(IOException::class)
fun closeDb() {
database.clearAllTables()
database.close()
}
@Test
@Throws(Exception::class)
fun insertAndGetUserFromDb_success() = testCoroutineRule.runBlockingTest {
// Given a database with 1 user
val newUser = UserFakes.get()
sut.insert(newUser)
// When fetching the user
sut.get().test {
// Then the user fields should match
val user = awaitItem()
assert(user.id == newUser.id)
assert(user.name == newUser.name)
}
}
}
附加说明:我正在使用Turbine来简化测试流程。这是上面代码中的一部分,我在流上调用.test {}
和awaitItem()
来等待流的结果。参见涡轮机自述。