1

Assume I have the following trait, with a single method that receives a call-by-name parameter:

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

Also, assume I have the following function:

final def getValue: String = {
  "value"
}

Now let's say I'm trying to Mock this class using Mockito (org.specs2.mock.Mockito), the following way:

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

The problem is that when the mocked method is invoked, it doesn't return the expected value:

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

As you can see, I'm using this parameter actually as a function I send to the method (a bit like a Supplier). I don't understand why the Mocking doesn't work. I cannot write my unit tests as long as I cannot control what client.compute(..) returns.

Help is much appreciated.

SergGr
  • 23,570
  • 2
  • 30
  • 51
Amit Pe'er
  • 173
  • 1
  • 12

2 Answers2

2

Call-by-name parameters are actually compiled into something like this:

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

and the call is converted into something like this

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

or putting it more explicitly:

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

So Mockito returns doesn't work because in two calls of the compute the parameter being passed is actually two different Function0 objects. And because equals is not overridden for Function0, they don't match.

The trick to work this around is to first explicitly convert your getValue method into a local Function0[String] variable. This seems to be enough to make two client.compute calls generate identical objects for Mockito to work. So you may try to use following code:

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

Update

If what you actually test is not client.compute but some other method in Java that inside calls client.compute and you want to mock that call, I don't know how to help you preserving exact semantics without rewriting at least some of your code. Probably the simplest thing I can think of is to use Funciton0 in the signature explicitly and then use Matchers.any such as

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

and then

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" 
}
SergGr
  • 23,570
  • 2
  • 30
  • 51
  • Perfect. Thanks a lot! – Amit Pe'er Jan 17 '18 at 20:09
  • Here's another one for you: I'm actually calling client.compute from Java class. so it would look like this: client.compute(() -> getValue). This way mocking does not work again as expected. any clues? – Amit Pe'er Jan 17 '18 at 20:21
  • @AmitPe'er, you mean that what you actually test is not `client.compute` but some other method in Java that inside calls `client.compute` and you want to mock that call? If that is the case, I don't know how to help you preserving exact semantics. See my update in the answer for some ideas that might or might not help you. – SergGr Jan 17 '18 at 21:01
  • Thanks - taking a look. And let's say I want to do the same thing, but from a Scala class instead of java? Would it be less trickier? – Amit Pe'er Jan 18 '18 at 10:31
  • @AmitPe'er, unless you have very rare code I don't think that calling it from Scala makes things easier. The problem still remains that you need to pass exactly the same argument to the mocking call as the one you pass to the real call. And most probably you don't save the value used in the real call to some field to be re-used in the mocking call. So the only alternative seems to be using `ArgumentMatchers.any` and I was not able to make it work without explicitly converting signature to `Function0` from `call-by-name`. – SergGr Jan 18 '18 at 13:51
  • ok, gotcha. Thing is, when I'm trying to perform what you did with Matchers.any(), I get the following run time error: "org.mockito.exceptions.misusing.InvalidUseOfMatchersException: Invalid use of argument matchers!" (After I changed compute signature like you said) – Amit Pe'er Jan 18 '18 at 14:14
  • (My 'compute' method actually receives a few more argument of String type, if that's related) – Amit Pe'er Jan 18 '18 at 14:24
  • @AmitPe'er yes this is most probably related. If you go with matchers, you should use matchers for all arguments. For example `client.compute2(ArgumentMatchers.eq(1), ArgumentMatchers.any()) returns "result"` assuming `def compute2(i: Int, value: () => String): String` – SergGr Jan 18 '18 at 14:28
0

The problem with the Mock is related to the fact that you are not explicitly declaring the return type of the compute method in the Client trait and given the fact that there is no body, it's inferred to be Unit.

So when you try to define the mock behaviour with

client.compute(getValue) returns "result"

you are not defining the behaviour for that method. You have to explicitly declare the return type for a public method

Luca T.
  • 1,641
  • 1
  • 14
  • 18
  • Actually, I do declare the return type. My bad, I accidentally didn't write it in my question snipped. Will edit – Amit Pe'er Jan 17 '18 at 19:27
  • So, maybe worth checking https://stackoverflow.com/questions/33487537/how-do-you-mock-scala-call-by-name-in-mockito – Luca T. Jan 17 '18 at 19:37