Kotlin中Mock全局属性无效,求CustomLogger测试解决方案
如何Mock Kotlin中CustomLogger的get()方法或init代码块
我遇到了和《Mock a "global" property in Kotlin》一文相同的问题,但无法在原问题下评论,因此重新提问。我尝试了文中所有解决方案均无效,目前正在为以下CustomLogger类编写测试用例,希望能Mock其get()方法或init代码块:
class CustomLogger constructor(val ctx: Context, embEnabled: Boolean = false) : Logger { private val loggers = arrayListOf<Logger>() fun get() = loggers init { if (embEnabled) loggers.add(Emb(ctx)) if (BuildConfig.DEBUG) loggers.add(DebugLogger(ctx)) } override fun logError(t: Throwable, msg: String?) { loggers.forEach { logger -> logger.logError(t, msg) } } }
方案1:重构类设计(最推荐)
直接调整CustomLogger的构造逻辑,把依赖的Logger实例作为参数传入,而非在init代码块中硬创建。这样测试时可以直接传入Mock对象,完全避开原有的初始化逻辑:
// 修改后的CustomLogger class CustomLogger constructor( val ctx: Context, private val loggers: List<Logger> = emptyList() ) : Logger { // 保留原构造方法做兼容(可选) constructor(ctx: Context, embEnabled: Boolean = false) : this( ctx, buildList { if (embEnabled) add(Emb(ctx)) if (BuildConfig.DEBUG) add(DebugLogger(ctx)) } ) override fun logError(t: Throwable, msg: String?) { loggers.forEach { it.logError(t, msg) } } }
测试示例(以MockK为例):
val mockLogger1 = mockk<Logger>() val mockLogger2 = mockk<Logger>() val customLogger = CustomLogger(mockk<Context>(), listOf(mockLogger1, mockLogger2)) // 验证logError调用逻辑 customLogger.logError(Throwable(), "test") verify { mockLogger1.logError(any(), any()) mockLogger2.logError(any(), any()) }
方案2:修改BuildConfig.DEBUG值(针对调试日志场景)
如果只是想阻止DebugLogger被添加到loggers中,可以通过反射修改BuildConfig.DEBUG的取值:
// 反射修改BuildConfig.DEBUG val debugField = BuildConfig::class.java.getField("DEBUG") debugField.isAccessible = true debugField.setBoolean(null, false)
执行这段代码后,CustomLogger的init代码块中就不会添加DebugLogger了。
方案3:部分Mock或反射修改loggers字段(无法修改原类时用)
如果不能改动原类代码,可通过部分Mock工具(如MockK的spyk)覆盖get()方法的返回值,或者直接反射修改private的loggers字段:
方式A:Mock get()方法
val mockLogger = mockk<Logger>() val customLogger = spyk(CustomLogger(mockk<Context>())) { on { get() } returns listOf(mockLogger) } // 验证logError调用 customLogger.logError(Throwable(), "test") verify { mockLogger.logError(any(), any()) }
方式B:反射修改loggers字段
因为init代码块在对象创建时就已执行,若要替换已初始化的loggers列表,可直接反射修改:
val customLogger = CustomLogger(mockk<Context>()) val mockLogger = mockk<Logger>() // 反射修改private的loggers字段 val loggersField = CustomLogger::class.java.getDeclaredField("loggers") loggersField.isAccessible = true loggersField.set(customLogger, arrayListOf(mockLogger)) // 验证调用逻辑 customLogger.logError(Throwable(), "test") verify { mockLogger.logError(any(), any()) }
内容的提问来源于stack exchange,提问作者Jitendra
相关产品推荐
相关产品推荐

