Mockito的thenReturn对数据类字段不生效问题及测试实践
问题描述
我定义了如下Kotlin数据类:
data class LoyaltyVoucher( val voucherNumber: String, // 其他字段... )
在Android仪器化测试中,尝试用Mockito mock该类的voucherNumber字段:
val serverVoucher = Mockito.mock(LoyaltyVoucher::class.java) Mockito.`when`(serverVoucher.voucherNumber).thenReturn("some number")
但调用serverVoucher.voucherNumber始终返回null。手动添加自定义getter方法后却能正常工作:
fun getVoucherNumberTest(): String { return voucherNumber }
测试代码改为:
val serverVoucher = Mockito.mock(LoyaltyVoucher::class.java) Mockito.`when`(serverVoucher.getVoucherNumberTest()).thenReturn("some number")
此时调用serverVoucher.getVoucherNumberTest()会返回预期的"some number"。
使用的依赖为:com.linkedin.dexmaker:dexmaker-mockito-inline:2.28.3
补充:希望了解数据类测试的通用实践,已知部分开发者认为mock数据类并非好做法。
原因分析
核心问题在于Kotlin数据类自动生成的属性getter方法的特殊性,以及Android仪器化测试中dexmaker-mockito-inline的限制:
- Kotlin中
val属性会自动生成私有字段和对应的final getter方法(如getVoucherNumber()),但这个getter属于编译器合成的方法,并非开发者手动定义的普通方法。 dexmaker-mockito-inline虽然支持mock final类和方法,但对Kotlin数据类自动生成的合成getter方法的拦截逻辑存在缺陷,无法正确存根其返回值。- 而你手动添加的
getVoucherNumberTest()是普通的final方法,dexmaker可以正常识别并拦截这个方法的调用,因此mock可以生效。
数据类测试通用实践
由于数据类的核心职责是承载纯数据,状态透明且无复杂业务逻辑,mock数据类通常是冗余且不推荐的,更合理的实践包括:
- 直接实例化数据类:用Kotlin的命名参数快速创建不同状态的实例,比如
LoyaltyVoucher(voucherNumber = "test-001", ...),完全满足测试场景需求。 - 封装测试工厂方法:如果测试中需要大量重复创建实例,可编写工厂方法统一生成,比如:
fun createTestVoucher(voucherNumber: String = "default-num") = LoyaltyVoucher(voucherNumber, /* 其他默认字段值 */) - 避免mock数据类:只有当数据类意外依赖了复杂外部组件(极罕见情况),才考虑mock;绝大多数场景下,直接使用真实实例比mock更可靠,还能避免mock带来的额外问题。
- 利用数据类的特性:Kotlin数据类自动实现了
equals()、hashCode()、toString(),测试中可以直接用这些方法验证数据正确性,无需额外mock逻辑。
内容的提问来源于stack exchange,提问作者Nik Myers
相关产品推荐
相关产品推荐

