MockK中Spyk模拟属性失效:调用getResult未返回预期对象
MockK spyk 属性模拟问题排查与解决
问题场景
在使用MockK测试Manager类时,已成功模拟myObject.ok属性,直接访问该属性的断言全部通过,但调用getResult()方法时返回的不是预期的mock实例,断言失败。
测试代码
val myObject = spyk<Manager>() // 定义属性行为 val mockOK = mockk<Result>() every { mockOK.id } returns "aaa" every { mockOK.status } returns SessionStatus.OK every { mockOK.timestamp } returns 123 every { mockOK.isValid() } returns true every { myObject.ok } returns mockOK // 使用模拟属性 val result = myObject.ok assertEquals("aaa", result.id) // 此断言通过 assertEquals(true, result.isValid()) // 此断言通过 val actual = myObject.getResult() assertEquals("aaa",actual.id) // 此断言失败
被测类代码
class Manager{ var ok : Result = Result() var notOk: Result = Result() fun getResult() = if (ok.isValid()) { ok } else notOk }
问题原因
问题出在spyk的属性mock方式和Kotlin编译器的内部优化:
- 当你用
every { myObject.ok } returns mockOK时,MockK仅拦截了外部调用的属性getter方法,所以测试代码中直接访问myObject.ok会返回mock实例。 - 但Kotlin编译器对类内部的属性访问会做优化:直接访问属性的幕后字段(
field),而非调用getter方法。因此getResult()内部访问ok时,拿到的是Manager实例初始化时的默认Result对象,而非你mock的mockOK。这就导致ok.isValid()返回默认值(false),最终getResult()返回notOk对象,断言失败。
解决方案
直接修改spyk实例的属性字段值,而非mock getter方法:
将测试代码中的:
every { myObject.ok } returns mockOK
替换为:
myObject.ok = mockOK
这样修改后,Manager实例的ok字段会被直接替换为mockOK,getResult()内部访问ok时拿到的就是mock实例,isValid()返回true,最终返回mockOK,断言即可通过。
另外,如果需要完全模拟Manager类而非使用部分模拟的spyk,也可以用mockk<Manager>(),并补充mockgetResult()的行为:
val myObject = mockk<Manager>() // ... 定义mockOK的行为不变 every { myObject.ok } returns mockOK every { myObject.getResult() } returns myObject.ok
内容的提问来源于stack exchange,提问作者bj4947
相关产品推荐
相关产品推荐

