Mockito验证接收函数参数的方法时失败问题排查
Why Mockito Fails to Verify
transform Call with a Function Argument? 这个问题的核心原因是Scala方法转函数时的实例唯一性问题,结合Mockito默认的参数匹配逻辑导致的:
问题根源
当你把withGreeting这个方法传递给transform时,Scala会自动执行「方法提升(method lifting)」,将其转换为Function1[DataFrame, DataFrame]的匿名实例。但注意:每次提升操作都会生成一个全新的函数对象。
而Mockito默认使用对象的equals方法来匹配方法调用的参数,Scala中这种自动生成的函数实例,其equals是基于引用地址的——也就是说,你在when语句里传递的函数实例,和实际调用transform时传递的函数实例,是两个完全不同的对象。这就导致Mockito认为「参数不匹配」,尽管错误信息里都显示<function1>,但它们的底层引用是不一样的。
解决方案
有几种简单的方式可以解决这个问题:
1. 将方法提取为固定的函数引用
把withGreeting定义为一个val类型的函数,而不是普通方法,这样每次传递的都是同一个函数实例:
import org.apache.spark.sql.DataFrame import org.scalatest.funsuite.AnyFunSuite import org.apache.spark.sql.functions._ import org.mockito.{Mockito, MockitoSugar} class SimpleTest extends AnyFunSuite with MockitoSugar { // 定义为val,确保全局只有一个函数实例 val withGreeting: DataFrame => DataFrame = _.withColumn("greeting", lit("hello")) test("sample test") { val mockDF = mock[DataFrame] val mockDF2 = mock[DataFrame] doReturn(mockDF2).when(mockDF).transform(withGreeting) mockDF.transform(withGreeting) val orderVerifier = Mockito.inOrder(mockDF) orderVerifier.verify(mockDF).transform(withGreeting) } }
2. 使用Mockito的引用匹配器(适合保留原有方法结构的场景)
如果你不想修改withGreeting的方法定义,可以使用Mockito的same匹配器,强制验证参数是同一个引用。不过这里需要先把方法显式转换为函数并保存引用:
import org.apache.spark.sql.DataFrame import org.scalatest.funsuite.AnyFunSuite import org.apache.spark.sql.functions._ import org.mockito.{Mockito, MockitoSugar} import org.mockito.ArgumentMatchers.same class SimpleTest extends AnyFunSuite with MockitoSugar { def withGreeting(df: DataFrame): DataFrame = { df.withColumn("greeting", lit("hello")) } test("sample test") { val mockDF = mock[DataFrame] val mockDF2 = mock[DataFrame] // 显式转换方法为函数并保存引用 val greetingFunc = withGreeting _ doReturn(mockDF2).when(mockDF).transform(greetingFunc) mockDF.transform(greetingFunc) val orderVerifier = Mockito.inOrder(mockDF) // 使用same匹配器验证引用一致 orderVerifier.verify(mockDF).transform(same(greetingFunc)) } }
为什么错误信息看起来参数一样?
Mockito在打印参数不匹配的错误时,只会输出函数的类型标识<function1>,而不会显示具体的引用信息,所以你会看到「Wanted」和「Actual」的参数看起来完全相同,但实际上它们是两个不同的对象实例。
内容的提问来源于stack exchange,提问作者coderplus
相关产品推荐
相关产品推荐

