【问题标题】:Verifying mocked object method calls with default arguments使用默认参数验证模拟对象方法调用
【发布时间】:2014-09-26 17:29:46
【问题描述】:

假设我有这个类:

class Defaults {
  def doSomething(regular: String, default: Option[String] = None) = {
    println(s"Doing something: $regular, $default")
  }
}

我想检查其他一些类在 Defaults 实例上调用 doSomething() 方法而不传递第二个参数:

defaults.doSomething("abcd")  // second argument is None implicitly

但是,模拟 Defaults 类无法正常工作。因为方法参数的默认值编译为同一类中的隐藏方法,mock[Defaults] 返回一个对象,其中这些隐藏方法返回null 而不是None,因此此测试失败:

class Test extends FreeSpec with ShouldMatchers with MockitoSugar {
  "Defaults" - {
    "should be called with default argument" in {
      val d = mock[Defaults]

      d.doSomething("abcd")

      verify(d).doSomething("abcd", None)
    }
  }
}

错误:

Argument(s) are different! Wanted:
defaults.doSomething("abcd", None);
-> at defaults.Test$$anonfun$1$$anonfun$apply$mcV$sp$1.apply$mcV$sp(Test.scala:14)
Actual invocation has different arguments:
defaults.doSomething("abcd", null);
-> at defaults.Test$$anonfun$1$$anonfun$apply$mcV$sp$1.apply$mcV$sp(Test.scala:12)

原因很清楚,但是否有合理的解决方法?我看到的唯一一个是使用spy() 而不是mock(),但是我的模拟类包含很多方法,在这种情况下我必须显式模拟,我不想要它。

【问题讨论】:

  • 很遗憾,如果您使用参数匹配器,则所有参数都必须由匹配器提供 docs.mockito.googlecode.com/hg/org/mockito/Mockito.html#3
  • @JhonnyEverson,我从未说过我在使用匹配器。您可以从我的示例中看到没有使用单个匹配器。
  • 啊,对,对不起。也许您可以使用一些隐式参数而不是默认参数,但我不确定它是否会起作用。只是一个想法。
  • 我不确定 Mockito 能做到这一点。在您的情况下,d 只是一个标准 Java 代理,它在内部调用Defaults 的真实实例。由于 Java 没有 Some/None 范式的概念,因此在动态绑定参数时它将使用 null - 对于 Java,您的 doSomething() 方法是一个普通的 2 参数方法,因此反射调用需要用一些东西填充 arg 列表,在你的情况下是null。顺便说一句,你试过用原生 ScalaMock 解决这个问题吗?

标签: scala mockito default-arguments


【解决方案1】:

这与 Scala 编译器如何将其实现为 Java 类有关,请记住 Scala 运行在 JVM 上,因此所有内容都需要转换为看起来像 Java 的东西

在这种特殊情况下,编译器所做的是创建一系列隐藏方法,这些方法将被称为 methodName$default$number 其中 number 是位置这个方法所代表的参数,然后,编译器将在每次我们调用这个方法时检查,如果我们没有为这个参数提供一个值,它会在它的位置插入一个对 $default$ 方法的调用,一个例子“编译”版本的内容是这样的(请注意,这并不是编译器所做的,但它可以用于教育目的)

class Foo {
   def bar(noDefault: String, default: String = "default value"): String
}
val aMock = mock[Foo]

aMock.bar("I'm not gonna pass the second argument")

最后一行将被编译为

aMock.bar("I'm not gonna pass the second argument", aMock.bar$default$1)

现在,因为我们在模拟上调用 bar$default$1,而 Mockito 的默认行为是返回 null 对于任何没有被存根的东西,那么最终执行的是类似的东西

aMock.iHaveSomeDefaultArguments("I'm not gonna pass the second argument", null)

这正是错误所告诉的......

为了解决这个问题,必须进行一些更改,以便 mockito 实际上调用真正的 $default$ 方法,因此替换正确完成

这项工作已在 mockito-scala 中完成,因此通过迁移到该库,您将获得解决此问题以及在 Scala 中使用 mockito 时可以找到的许多其他问题的解决方案

免责声明:我是 mockito-scala 的开发者

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-03-28
    • 1970-01-01
    • 2011-08-04
    • 1970-01-01
    • 1970-01-01
    • 2020-02-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多