Search code examples
scalamockitospecs2

Mock is returning stubbed result for arbitrary param


With the following test I have an invalid stub setup. The mock requestBuilder is stubbed with the param "INCORRECT", whereas the class under test invokes it with "/socket".

My experience with Mockito in JUnit tells me that the invocation at runtime will result in null. However, I'm seeing a false positive. The test passes & the requestBuilder is returning the mock request, even though it is invoked with "/socket".

Why is this? Has it something to do with having more than one expectation? If so, the final expectation is the one that counts and it should fail.

class ChannelSpec extends Specification with Mockito with ScalaCheck with ArbitraryValues { def is = s2"""

  A Channel must
    send an open request to /socket with provided channel name on instantiation $sendToSocket

"""

  def sendToSocket = prop{(name: String, key: Key, secret: Secret) =>
    val requestBuilder = mock[(String, Seq[Map[String, String]]) => Req]
    val request = mock[Req]
    val httpRequestor = mock[(Req) => Future[String]]
    val result = mock[Future[String]]

    val params = Seq(Map("key" -> key.value, "timestamp" -> anyString, "token" -> anyString), Map("channel" -> name))
    requestBuilder("INCORRECT", params) returns request
    httpRequestor(request) returns result
    new Channel(name, key, secret, requestBuilder = requestBuilder, httpRequestor = httpRequestor)
    there was one(requestBuilder).apply("INCORRECT", params)
    println("expecting " + request)
    there was one(httpRequestor).apply(request)
  }

Solution

  • While I don't know Scala, I do know that anyString doesn't do what you think it does. Specifically, all Mockito matchers work through side effects, adding the related String objects to ArgumentMatcherStorage as I described toward the end of this SO answer.

    Consequently, you can't really extract matchers into variables:

    // This is fine, because the parameters are evaluated in order.
    
    takesTwoParameters(eq("A"), eq("B")) returns bar
    
    // Now let's change it a little bit.
    
    val secondParam = eq("SECOND")
    val firstParam = eq("FIRST")
    
    // At this point, firstParam == secondParam == null, and the hidden argument
    // matcher stack looks like [eq("SECOND"), eq("FIRST")]. That means that your
    // stubbing will apply to takesTwoParameters("SECOND", "FIRST"), not
    // takesTwoParameters("FIRST", "SECOND")!
    
    takesTwoParameters(firstParam, secondParam)) returns bar
    

    Furthermore, you can't nest anyString into arbitrary types like Seq or Map; Mockito's argument matchers are designed to match entire arguments.

    Your best bet is to use argThat for your params, and create a small Hamcrest matcher that checks that the argument you're checking is properly formed. That will give you the flexibility of checking that your params are roughly what you expect, while ignoring the values you don't care about.