Search code examples
scalaunit-testingmockitonamed-parameters

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.


Solution

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