使用默认参数验证模拟对象方法调用 [英] Verifying mocked object method calls with default arguments

查看:101
本文介绍了使用默认参数验证模拟对象方法调用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我有这个课程:

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

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

I want to check that some other class invokes doSomething() method on Defaults instance without passing second argument:

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

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

However, mocking Defaults class does not work correctly. Because default values for method arguments are compiled as hidden methods in the same class, mock[Defaults] returns an object in which these hidden methods return null instead of None, so this test fails:

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(),但是我的模拟类包含很多方法,在这种情况下,我将必须显式地模拟它们,而我则不需要.

The reason of this is clear, but is there a sensible workaround? The only one I see is to use spy() instead of mock(), but my mocked class contains a lot of methods which I will have to mock explicitly in this case, and I don't want it.

推荐答案

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

This is related with how the Scala compiler implements this as a Java class, remember that Scala runs on the JVM, so everything needs to be transformed to something that looks like Java

在这种情况下,编译器要做的是创建一系列隐藏方法,这些方法将称为 methodName $ default $ number ,其中 number 是位置该方法表示的参数的值,然后,编译器将在每次调用此方法时进行检查,如果我们不为该参数提供值,它将在其位置插入对$ default $方法的调用.的编译"版本将是这样的(请注意,这并不是编译器所做的,但它可以用于教育目的)

In this particular case, what the compiler does is to create a series of hidden methods which will be called something like methodName$default$number where number is the position of the argument this method is representing, then, the compiler will check every time we call this method and if we don’t provide a value for such parameter, it will insert a call to the $default$ method in its place, an example of the "compiled" version would be something like this (note that this is not exactly what the compiler does, but it works for educational purposes)

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,所以最终执行的操作类似于

Now, because we are making the call to bar$default$1 on a mock, and the default behaviour of Mockito is to return null for anything that hasn’t been stubbed, then what ends up executing is something like

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

错误确切地说明了什么……

Which is exactly what the error is telling…

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

In order to solve this some changes have to be made so mockito actually calls the real $default$ methods and so the replacement is done correctly

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

This work has been done in mockito-scala, so by migrating to that library you'll get a solution for this and many other problems that can be found when mockito is used in Scala

免责声明:我是模仿itoscala的开发者

Disclaimer: I'm a developer of mockito-scala

这篇关于使用默认参数验证模拟对象方法调用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆