You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin中如何为实现KoinComponent的类编写单元测试

问题根源

  1. Mockito报错Mockito cannot mock this class: 是因为PhoneHelper是Kotlin的object单例类,默认是final类型,原生Mockito不支持mock final类和Kotlin单例。
  2. 直接调用时报错Koin has not started. 是因为PhoneHelper实现了KoinComponent接口,依赖Koin容器注入PhoneNumberUtil实例,单元测试环境默认没有初始化Koin,找不到对应依赖就会抛出该错误。

可选解决方案

方案一:使用MockK库完成单例Mock(无需修改原有业务代码)

MockK对Kotlin原生特性适配更好,支持直接mock单例类:

  1. 首先在test模块的依赖配置中添加MockK依赖:
testImplementation "io.mockk:mockk:1.13.8"
  1. 测试代码示例:
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并注入对应的依赖:

  1. 首先添加Koin测试依赖:
testImplementation "io.insert-koin:koin-test:3.4.0"
  1. 测试代码示例:
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的强绑定:

  1. 重构后的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())) }
  1. 测试代码示例(无需特殊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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 09:15:04