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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 17:25:36