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

如何使用MockK Stub派生类实例中基类的protected函数?

如何用MockK Stub基类的protected函数?

现有代码

基类代码

abstract class BaseNotification<T: Any> {

    protected abstract fun convertToJson(message: T): JSONObject?

    companion object {
        @JvmStatic
        @Throws(JSONException::class)
        protected fun convertMapToJson(dataMap: Map<String, String>): JSONObject {
            val result = JSONObject()
            for ((key, value) in dataMap) {
                result.put(key, value)
            }
            return result
        }
    }
}

派生类代码

class DerivedNotification private constructor() : BaseNotification<Intent>() {

    @VisibleForTesting
    internal fun doConvertJson(message: Intent) : JSONObject? = convertToJson(message)

    fun getMap(bundle: Intent): MutableMap<String, String> {
        val bundleToMap: MutableMap<String, String> = HashMap()
        for (key in bundle.keySet()) {
            val value = bundle.getString(key)
            if (value != null) {
                bundleToMap[key] = value
            }
        }
        return bundleToMap
    }

    override fun convertToJson(message: Intent): JSONObject? {
        val bundleToMap: MutableMap<String, String> = getMap(message)
        var jsonObject: JSONObject? = null
        jsonObject = try {
            convertMapToJson(bundleToMap)
        } catch (e: JSONException) {
            return null
        }
        return jsonObject
    }
}

编译失败的测试代码

@Test
fun test_convertToJson() {
        val derivedNotification = spyk(DerivedNotification())
        every { derivedNotification.getMap(any()) } returns mapWithData

        val intentMock = mockk<Intent>(relaxed = true)

        // Mock the doConvertMapToJson method to throw a JSONException
        every { derivedNotification.convertMapToJson(any()) } throws JSONException("+++ !!! exp")
        //test
        val jsonObj = derivedNotification.doConvertJson(intentMock)
        // verify
        assertNull(jsonObj)
        
    }

问题核心

convertMapToJson是基类伴生对象的protected函数,测试类无法直接访问该函数,且原测试代码错误地试图在派生类实例上调用伴生对象的函数,导致编译失败。

解决方案

方法1:Mock伴生对象的protected函数(推荐)

利用MockK的mockkStaticAPI来mock基类的伴生对象,直接Stub目标protected函数的行为:

@Test
fun test_convertToJson() {
    val derivedNotification = spyk(DerivedNotification())
    every { derivedNotification.getMap(any()) } returns mapWithData

    val intentMock = mockk<Intent>(relaxed = true)

    // Mock基类伴生对象的protected函数
    mockkStatic(BaseNotification.Companion::class)
    every { BaseNotification.convertMapToJson(any()) } throws JSONException("+++ !!! exp")

    // 执行测试
    val jsonObj = derivedNotification.doConvertJson(intentMock)
    
    // 验证结果
    assertNull(jsonObj)
    
    // 清理mock
    unmockkStatic(BaseNotification.Companion::class)
}

方法2:通过反射调整可见性(兼容特殊场景)

如果无法直接mock伴生对象,可以通过反射解除函数的protected限制,再结合MockK进行Stub:

@Test
fun test_convertToJson() {
    val derivedNotification = spyk(DerivedNotification())
    every { derivedNotification.getMap(any()) } returns mapWithData

    val intentMock = mockk<Intent>(relaxed = true)

    // 通过反射获取伴生对象的protected函数
    val convertMethod = BaseNotification.Companion::class.java
        .getDeclaredMethod("convertMapToJson", Map::class.java)
    convertMethod.isAccessible = true

    // Mock伴生对象的函数行为
    mockkStatic(BaseNotification.Companion::class)
    every { convertMethod.invoke(BaseNotification.Companion, any<Map<String, String>>()) } 
        throws JSONException("+++ !!! exp")

    val jsonObj = derivedNotification.doConvertJson(intentMock)
    assertNull(jsonObj)
    
    unmockkStatic(BaseNotification.Companion::class)
}

关键说明

  • 注意区分实例protected函数和伴生对象protected函数:本次场景中的目标函数属于伴生对象,必须mock伴生对象而非派生类实例。
  • mockkStatic用于处理静态/伴生对象的mock,测试结束后建议调用unmockkStatic清理,避免影响其他测试用例。

内容的提问来源于stack exchange,提问作者lannyf

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 05:07:38