Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Verifying mocked object method calls with default arguments

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.

like image 656
Vladimir Matveev Avatar asked Aug 04 '14 09:08

Vladimir Matveev


People also ask

How do you know if mocked method called?

To check if a method was called on a mocked object you can use the Mockito. verify method: Mockito. verify(someMock).

What is eq() in Mockito?

eq(obj) checks that the argument equals obj according to its equals method. This is also the behavior if you pass in real values without using matchers. Note that unless equals is overridden, you'll see the default Object. equals implementation, which would have the same behavior as same(obj) .

How to mock any object in Mockito?

Mockito allows us to create mock objects and stub the behavior for our test cases. We usually mock the behavior using when() and thenReturn() on the mock object.


1 Answers

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

like image 157
Bruno Avatar answered Oct 14 '22 00:10

Bruno