多数情况下,Application 会被作为一个全局对象提供功能。通常的作法是给 Application 添加一个静态方法来获取其实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class MyApplication : Application() {
companion object {
lateinit var sBaseApp: IApplication
val instance: IApplication
get() = sBaseApp
}
init {
sBaseApp = this
}
fun doSomeThing(){
// balah balah
}
}
class XXActivity{
override fun onCreate(savedState:Bundle?){
MyApplication.instance.doSomeThing()
}
}
但是这样带来的问题是,怎么脱离复杂的 Application 类进行单元测试? 纯用 mockito
和 robolectic
是无法达到目的的。这时就需要用到强大的 powerMock
了:
1
Whitebox.setInternalState(MyApplication::class.java,"sBaseApp",app) // Powermock 提供反射修改属性的方法。
当然,只是上面还不够,还需要对 MyApplication 进行改造,抽象出一个 IApplication
, 添加获取 context 的接口及其他普通接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface IApplication {
val context: Application // 通常这个用于提供 context
fun doSomeThing() // 其他能用接口
}
class MyApplication : Application(),IApplication {
companion object {
@JvmStatic
lateinit var sBaseApp: IApplication
@JvmStatic
val instance: IApplication
get() = sBaseApp
}
init {
sBaseApp = this
}
override val context = this // -------------- override
override fun doSomeThing(){ // -------------- override
// balah balah
}
}
相关测试代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RunWith(AndroidJUnit4::class)
@Config(application = VApp::class, sdk = [28]) // 指定运行测试时使用的 Application 实例
class MainActivityTest {
@Before
fun setUp() {
// 获取当前 Application 实例
val app = ApplicationProvider.getApplicationContext<VApp>()
// 通过 powerMock 反射修改 MyApplication 的对象
Whitebox.setInternalState(MyApplication::class.java,"sBaseApp",app)
}
@Test
fun testMain(){
val activity = Robolectric.setupActivity(MainActivity::class.java)
}
}
关键词:UninitializedPropertyAccessException, lateinit, kotlin , companion object, mockito, powermock, robolectric
kotlin.UninitializedPropertyAccessException: lateinit property sBaseApp has not been initialized