如何不mock实例化带私有构造、伴生对象的Kotlin继承类做单元测试?
单元测试中直接实例化私有构造GpsTrackingService的实现方案
核心适配逻辑
当前类的私有构造函数、空伴生对象结构,完全可以在不mock GpsTrackingService本身的前提下,通过3种低侵入方案拿到真实实例开展测试:
调整构造函数可见性为internal
Kotlin中internal修饰的构造函数仅对同模块内的代码开放访问权限,同模块下的生产业务代码、单元测试代码都可直接调用,不会把构造函数暴露给模块外的第三方调用,和原有private的对外访问效果基本一致。
调整后的生产类代码示例:class GpsTrackingService internal constructor( private val context: Context, private val snavigator: SNavigator? ) : LocationListener { // 保留原有的空伴生对象结构,可把线上正式的实例获取逻辑迁移到伴生对象中 companion object {} fun getLastKnownLocation(): Location? { // 原有业务逻辑 return null } }测试代码中可直接实例化:
// 生成依赖对象(仅mock Context、SNavigator这类外部依赖,不mock GpsTrackingService本身) val testContext = mock(Context::class.java) val testNavigator = mock(SNavigator::class.java) // 直接构造真实GpsTrackingService实例 val realGpsService = GpsTrackingService(testContext, testNavigator) // 执行待测方法开展断言 val location = realGpsService.getLastKnownLocation()保留私有构造的前提下通过反射实例化
完全不改动生产类的可见性规则时,可在测试代码中通过反射绕过私有构造的访问限制,直接生成真实实例:
测试代码示例:val testContext = mock(Context::class.java) val testNavigator = mock(SNavigator::class.java) // 匹配私有构造的参数签名 val privateConstructor = GpsTrackingService::class.java.getDeclaredConstructor( Context::class.java, SNavigator::class.java ).apply { isAccessible = true // 关闭私有成员的访问检查 } // 生成真实类实例 val realGpsService = privateConstructor.newInstance(testContext, testNavigator)利用伴生对象增加测试专用工厂方法
现有空伴生对象可直接添加仅测试使用的实例构建方法,配合@VisibleForTesting标注提醒不要在生产业务中调用,既保留私有构造的对外访问限制,也不需要反射逻辑:
生产类调整示例:class GpsTrackingService private constructor( private val context: Context, private val snavigator: SNavigator? ) : LocationListener { companion object { // 测试专用构建方法,标注后可通过Lint检查提醒非测试场景不要调用 @androidx.annotation.VisibleForTesting internal fun buildForTest(context: Context, navigator: SNavigator?): GpsTrackingService { return GpsTrackingService(context, navigator) } } fun getLastKnownLocation(): Location? { // 原有业务逻辑 return null } }测试代码中直接调用伴生对象的方法即可拿到真实实例,无需额外反射逻辑。
内容的提问来源于stack exchange,提问作者Mahriban
相关产品推荐
相关产品推荐

