Scala - 模拟一个接收按名称调用参数的方法

Scala - Mock a method that receives a call-by-name parameter

假设我有以下特征,有一个接收按名称调用参数的方法:

trait Client { 
    def compute(value: => String): String
}

此外,假设我有以下功能:

final def getValue: String = {
  "value"
}

现在假设我正在尝试使用 Mockito (org.specs2.mock.Mockito) 模拟此 class,方法如下:

val client: Client = mock[Client]
client.compute(getValue) returns "result"

问题是当调用模拟方法时,它没有 return 预期值:

client.compute(getValue) mustEqual "result" // fails. returns null

如您所见,我实际上将此参数用作发送到方法的函数(有点像 Supplier)。我不明白为什么 Mocking 不起作用。只要我无法控制 client.compute(..) returns.

,我就无法编写单元测试

非常感谢您的帮助。

Mock 的问题与以下事实有关:您没有在 Client 特性中明确声明 compute 方法的 return 类型,并且考虑到存在的事实是no body,推断为Unit.

因此,当您尝试使用

定义模拟行为时
client.compute(getValue) returns "result"

您没有定义该方法的行为。 您必须为 public 方法显式声明 return 类型

按名称调用的参数实际上编译成这样:

trait Client { 
    def compute(valueFunction => Function0[String]): String
}

调用被转换成这样

client.compute(() => { getValue() })

或者更明确地说:

client.compute(new Funciton0[String]{ def apply():String = { getValue() }})

所以 Mockito returns 不起作用,因为在 compute 的两次调用中,传递的参数实际上是两个不同的 Function0 对象。因为 equals 没有被 Function0 覆盖,所以它们不匹配。

解决这个问题的技巧是首先将您的 getValue 方法显式转换为本地 Function0[String] 变量。这似乎足以让两个 client.compute 调用为 Mockito 工作生成相同的对象。所以你可以尝试使用下面的代码:

import org.specs2._
import org.specs2.mock.Mockito

class Specs2Test extends Specification with Mockito {
  override def is =
    s2"""
   this works                 $good
   this doesn't               $bad
   """

  final def getValue: String = {
    "value"
  }

  def good = {
    val client: Client = mock[Client]
    val f: Function0[String] = getValue _
    client.compute(f()) returns "result"
    client.compute(f()) mustEqual "result"
  }

  def bad = {
    val client: Client = mock[Client]
    client.compute(getValue) returns "result"
    client.compute(getValue) mustEqual "result" // fails. returns null
  }
}

更新

如果您实际测试的不是 client.compute,而是 Java 中调用 client.compute 的其他方法,并且您想模拟该调用,我不知道如何帮助您在不重写至少部分代码的情况下保留准确的语义。可能我能想到的最简单的事情就是在签名中显式使用 Funciton0 然后使用 Matchers.any 例如

trait Client {
  def compute(value: () => String): String
}

然后

def usingMatchAny = {
  val client: Client = mock[Client]
  client.compute(ArgumentMatchers.any()) returns "result"
  // actually here you call the method that uses client.compute call
  client.compute(getValue _) mustEqual "result" 
}