Dagger Hilt单元测试如何为EntryPointAccessors提供依赖解决上下文空报错
解决方案
首先推荐成本最低的重构方案,其次是兼容现有代码的Mock方案:
方案1:重构解耦(最推荐)
你现在的代码把EntryPoint调用硬编码在父类构造参数的默认值里,本质是把依赖获取和类的实现耦合了,只要稍微调整构造参数就能完全避开测试时调用EntryPoint的问题:
// 原有构造逻辑保留默认值,不影响线上业务代码 abstract class AbstractSomethingImportantBuilder( val foo: Foo = EntryPointAccessors.fromApplication(applicationContext, FooEntryPoint::class.java).provideFoo() ) // 单元测试时直接传入mock的*Foo*实例,不会触发默认值的EntryPoint调用 val testBuilder = SomethingImportantBuilderImpl(foo = mockk(relaxed = true))
这种方案不需要修改任何业务逻辑,也不需要Mock复杂的Android框架类,测试性能和稳定性最高。
方案2:不重构代码,正确使用Mockk完成静态方法Mock
如果不方便修改现有代码,可以按以下步骤修复你的Mock逻辑:
第一步:修复setup方法不执行的问题
你遇到的lateinit property context has not been initialized报错,90%以上是因为你使用了Junit5测试框架,@Before是Junit4的注解,Junit5需要替换为@BeforeEach才能保证每个测试用例执行前都会调用setup方法。
第二步:正确Mock静态类与调用链
EntryPointAccessors是Java静态工具类,Mockk默认不会Mock静态方法,需要显式声明:
private lateinit var mockFoo: Foo private lateinit var mockEntryPoint: FooEntryPoint @BeforeEach // Junit5用这个,Junit4保留@Before fun setup() { // 1. 声明Mock静态类 mockkStatic(EntryPointAccessors::class) // 2. 初始化mock的*Foo*和EntryPoint实例 mockFoo = mockk(relaxed = true) mockEntryPoint = mockk { every { provideFoo() } returns mockFoo } // 3. stub EntryPoint的调用逻辑,不需要实际传入context every { EntryPointAccessors.fromApplication(any(), FooEntryPoint::class.java) } returns mockEntryPoint } @Test fun testBuilder() { // 直接初始化即可,构造里的EntryPoint调用会返回你mock的实例 val builder = SomethingImportantBuilderImpl() // 后续测试逻辑 }
如果你的代码里直接引用了全局的applicationContext变量,额外加一行stub即可:
// 加在setup方法里,mock全局context val mockContext = mockk<Context> { every { applicationContext } returns this } // 如果你用的是全局的top-level变量applicationContext,mock这个变量 every { applicationContext } returns mockContext
之前方案失败的原因
- 第一种Mock方案失败,是因为你没有显式声明Mock
EntryPointAccessors的静态方法,同时把provideFoo()的调用直接写在every的匹配规则里,调用链拆分后即可正常匹配。 - 第二种Mock方案的lateinit报错核心是setup方法未执行,替换注解即可解决,额外需要给mock的Application stub
getApplicationContext方法返回自身,避免Hilt内部调用时拿不到上下文。
内容的提问来源于stack exchange,提问作者Barrufet
相关产品推荐
相关产品推荐

