简体   繁体   中英

Scala - Mock a method that receives a call-by-name parameter

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.

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM