Scala中如何正确测试调用同类方法的Case Class方法?
问题
我有一个表示电话号码的Case Class,它提供了一些在代码其他地方使用的辅助方法,其中一个方法isPossiblyUsable调用了同类中的另外两个方法isUsable和usabilityUnknown。我想对这个方法进行单元测试,出于Python开发背景,我尝试Mock这两个依赖方法,但测试失败,报错显示未调用预期的方法。
Case Class代码
case class PhoneNumber { // 类数据和方法 def isPossiblyUsable(): Boolean = { // 两个方法均返回Boolean this.isUsable() || this.usabilityUnknown() } }
测试代码
"PhoneNumber.isPossiblyUsable" should "return true when usable" in { val m = mock[PhoneNumber] (m.isUsable _).expects().returning(true) (m.usabilityUnknown _).expects().returning(false) assert(m.isPossiblyUsable() == true) }
错误信息
[info] PhoneNumber.isPossiblyUsable [info] - should return true when usable *** FAILED *** [info] Unexpected call: <mock-1> PhoneNumber.isPossiblyUsable() [info] [info] Expected: [info] inAnyOrder { [info] <mock-1> PhoneNumber.isUsable() once (never called - UNSATISFIED) [info] <mock-1> PhoneNumber.usabilityUnknown() once (never called - UNSATISFIED) [info] } [info] [info] Actual: [info] <mock-1> PhoneNumber.isPossiblyUsable() (Option.scala:201)
解决方案
问题根源
你用mock[PhoneNumber]创建的是完全模拟对象,这个对象的所有方法(包括isPossiblyUsable)都是框架生成的空实现,不会执行原类中的真实逻辑。所以调用m.isPossiblyUsable()时,并不会触发原代码里的this.isUsable() || this.usabilityUnknown()逻辑,自然不会调用你预期的两个方法。
正确实现方式
要测试isPossiblyUsable的逻辑,有两种靠谱的做法:
使用部分模拟(Partial Mock)
保留原类isPossiblyUsable的真实实现,仅MockisUsable和usabilityUnknown这两个依赖方法。以Mockito为例,代码可修改为:"PhoneNumber.isPossiblyUsable" should "return true when usable" in { val phoneNumber = spy(new PhoneNumber()) // spy创建真实实例,允许替换指定方法 doReturn(true).when(phoneNumber).isUsable() doReturn(false).when(phoneNumber).usabilityUnknown() assert(phoneNumber.isPossiblyUsable() == true) }spy会生成一个真实的PhoneNumber实例,但允许你覆盖指定方法的返回值,这样isPossiblyUsable会执行原逻辑,并调用被Mock的两个方法。直接测试真实实例,通过状态控制依赖方法返回
如果isUsable和usabilityUnknown的返回值由PhoneNumber的内部状态(比如构造参数)决定,直接创建真实实例并设置对应状态即可:"PhoneNumber.isPossiblyUsable" should "return true when usable" in { // 假设通过构造参数控制isUsable的返回结果 val phoneNumber = PhoneNumber(/* 传入使isUsable返回true的参数 */) assert(phoneNumber.isPossiblyUsable() == true) }这种方式更符合单元测试的核心原则——测试真实业务逻辑,避免过度依赖Mock。
总结
Scala的Mock框架(如Mockito、ScalaMock)中,mock和spy的职责区分严格:mock是完全模拟,不会执行原类方法;spy(或ScalaMock中的partialMock)才是保留真实逻辑、仅替换指定方法的部分模拟方式。你的Python开发习惯在这里不适用,因为Python的Mock默认行为和Scala框架有差异。
内容的提问来源于stack exchange,提问作者James Kelleher

