1

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)
  }
Synesso
  • 37,610
  • 35
  • 136
  • 207

2 Answers2

1

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.

Community
  • 1
  • 1
Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • Right. I usually first write my intented test in this way and then wait for the hamcrest errors to tell me to write the more difficult "all matchers or none" code. But it seems in Specs2 the behaviour of the matchers is different. – Synesso Sep 05 '13 at 07:00
1

I think this is simply the manifestation that, in an acceptance specifications, there are no exceptions to signal failed expectations. So you you have:

def test = {
  1 === 2
  1 === 1
}

Then test is going to pass because only the last value will be kept as the test result. You can change this behaviour by either chaining expectations:

def test = {
  (1 === 2) and
  (1 === 1)
}

Or by mixing in the Specification the ThrownExpectations trait:

import org.specs2.Specification
import org.specs2.matcher.ThrownExpectations
import org.specs2.mock.Mockito

class MySpec extends Specification with ThrownExpecations with Mockito {
  ...
}
Eric
  • 15,494
  • 38
  • 61
  • So in the expression `requestBuilder("/socketz", params) returns request` the hamcrest exceptions are swallowed and the mock is returned anyway. If I change params to have no matchers then the spec fails on the last line because `request` is null. – Synesso Sep 05 '13 at 07:02
  • 1
    Jeff Bowman's answer is correct. My answer is then merely pointing out that `there was one(requestBuilder).apply("INCORRECT", params)` will not throw an exception in an acceptance spec even if the stubbing is done correctly. – Eric Sep 05 '13 at 07:17
  • I've tried to replicate what I'm seeing in a simplified test-case and I can't. The `InvalidUseOfMatchersException` is thrown as expected. So there's something odd about my spec that makes the matchers erroneously match. – Synesso Sep 05 '13 at 07:37