Kotlin中如何为实现KoinComponent的类编写单元测试
问题根源
- Mockito报错
Mockito cannot mock this class:是因为PhoneHelper是Kotlin的object单例类,默认是final类型,原生Mockito不支持mock final类和Kotlin单例。 - 直接调用时报错
Koin has not started.是因为PhoneHelper实现了KoinComponent接口,依赖Koin容器注入PhoneNumberUtil实例,单元测试环境默认没有初始化Koin,找不到对应依赖就会抛出该错误。
可选解决方案
方案一:使用MockK库完成单例Mock(无需修改原有业务代码)
MockK对Kotlin原生特性适配更好,支持直接mock单例类:
- 首先在test模块的依赖配置中添加MockK依赖:
testImplementation "io.mockk:mockk:1.13.8"
- 测试代码示例:
class WelcomeFragmentTest{ @Before fun setUp() { // 初始化PhoneHelper单例的mock mockkObject(PhoneHelper) } @Test fun `checkEditText Test`(){ val phoneNo= "558887888" // 定义mock方法的返回值 every { PhoneHelper.isValid(phoneNo,null) } returns false val bool = PhoneHelper.isValid(phoneNo,null) assertEquals(false, bool) } @After fun tearDown() { // 清除单例mock状态,避免影响其他测试用例 unmockkObject(PhoneHelper) } }
方案二:初始化测试用Koin容器,直接调用真实逻辑
如果需要测试PhoneHelper.isValid的真实业务逻辑,可以在测试环境启动Koin并注入对应的依赖:
- 首先添加Koin测试依赖:
testImplementation "io.insert-koin:koin-test:3.4.0"
- 测试代码示例:
class WelcomeFragmentTest: KoinTest { private val testPhoneNumberUtil = PhoneNumberUtil.createInstance(ApplicationProvider.getApplicationContext()) @Before fun setUp() { // 启动测试用Koin容器,注册依赖 startKoin { modules( module { single { testPhoneNumberUtil } } ) } } @Test fun `checkEditText Test`(){ val phoneNo= "558887888" // 直接调用真实的PhoneHelper.isValid方法 val bool = PhoneHelper.isValid(phoneNo,null) // 按照真实逻辑判断返回值即可 assertEquals(false, bool) } @After fun tearDown() { // 关闭Koin容器清理状态 stopKoin() } }
方案三:重构代码解耦(长期可测试性最优)
将PhoneHelper的依赖从硬编码Koin注入改为构造函数传入,消除对Koin的强绑定:
- 重构后的PhoneHelper代码:
class PhoneHelper(private val mPhoneNumberUtil: PhoneNumberUtil) { fun isValid(gsmNo: String?, countryCode: String?): Boolean { val phoneModel = Phonenumber.PhoneNumber() phoneModel.nationalNumber = gsmNo?.toLongOrNull() ?: 0 phoneModel.countryCode = if (countryCode?.contains("+")==true) countryCode.removePrefix("+").toIntOrNull() ?: 90 else 90 return mPhoneNumberUtil.isValidNumber(phoneModel) } } // 业务代码中在Koin模块里注册单例即可 // single { PhoneHelper(PhoneNumberUtil.createInstance(androidContext())) }
- 测试代码示例(无需特殊mock框架或Koin依赖):
class WelcomeFragmentTest{ private val mockPhoneNumberUtil = Mockito.mock(PhoneNumberUtil::class.java) private val phoneHelper = PhoneHelper(mockPhoneNumberUtil) @Test fun `checkEditText Test`(){ val phoneNo= "558887888" Mockito.`when`(mockPhoneNumberUtil.isValidNumber(Mockito.any())).thenReturn(false) val bool = phoneHelper.isValid(phoneNo,null) assertEquals(false, bool) } }
内容的提问来源于stack exchange,提问作者Ahmet Yilmaz
相关产品推荐
相关产品推荐

