Verifying mocked object method calls with default arguments

不想你离开。 提交于 2019-12-07 01:40:49

问题


Suppose I have this class:

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

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

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)
    }
  }
}

The error:

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)

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.


回答1:


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

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")

The last line would be compiled as

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

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…

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

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

Disclaimer: I'm a developer of mockito-scala



来源:https://stackoverflow.com/questions/25115329/verifying-mocked-object-method-calls-with-default-arguments

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!